diff --git a/.gitignore b/.gitignore index 476938f0..e314ed94 100644 --- a/.gitignore +++ b/.gitignore @@ -6,7 +6,7 @@ *.user *.userosscache *.sln.docstates - +*.DS_Store # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e6f89046..5174ce46 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,8 +2,8 @@ We love to receive contributions from the community so please keep them coming : 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 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 :) diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 3f0d0f3a..ddb30a7c 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -1,7 +1,7 @@ ## Expected Behavior / New Feature -## Actual Behavior / Motivation for New Feautre +## Actual Behavior / Motivation for New Feature ## Steps to Reproduce the Problem diff --git a/README.md b/README.md index e3128609..94bbaf13 100644 --- a/README.md +++ b/README.md @@ -81,11 +81,11 @@ We love to receive contributions from the community so please keep them coming : 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 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 diff --git a/docs/features/administration.rst b/docs/features/administration.rst index dd08feab..29dc142b 100644 --- a/docs/features/administration.rst +++ b/docs/features/administration.rst @@ -1,111 +1,139 @@ -Administration -============== - -Ocelot supports changing configuration during runtime via an authenticated HTTP API. This can be authenticated in two ways either using Ocelot's -internal IdentityServer (for authenticating requests to the administration API only) or hooking the administration API authentication into your own -IdentityServer. - -Providing your own IdentityServer -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -All you need to do to hook into your own IdentityServer is add the following to your ConfigureServices method. - -.. code-block:: csharp - - public virtual void ConfigureServices(IServiceCollection services) - { - Action options = o => { - // o.Authority = ; - // o.ApiName = ; - // etc.... - }; - - services - .AddOcelot() - .AddAdministration("/administration", options); - } - -You now need to get a token from your IdentityServer and use in subsequent requests to Ocelot's administration API. - -This feature was implemented for `issue 228 `_. It is useful because the IdentityServer authentication -middleware needs the URL of the IdentityServer. If you are using the internal IdentityServer it might not alaways be possible to have the Ocelot URL. - -Internal IdentityServer -^^^^^^^^^^^^^^^^^^^^^^^ - -The API is authenticated using bearer tokens that you request from Ocelot iteself. This is provided by the amazing -`Identity Server `_ project that I have been using for a few years now. Check them out. - -In order to enable the administration section you need to do a few things. First of all add this to your -initial Startup.cs. - -The path can be anything you want and it is obviously reccomended don't use -a url you would like to route through with Ocelot as this will not work. The administration uses the -MapWhen functionality of asp.net core and all requests to {root}/administration will be sent there not -to the Ocelot middleware. - -The secret is the client secret that Ocelot's internal IdentityServer will use to authenticate requests to the administration API. This can be whatever you want it to be! - -.. code-block:: csharp - - public virtual void ConfigureServices(IServiceCollection services) - { - services - .AddOcelot() - .AddAdministration("/administration", "secret"); - } - -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 -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 -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. - -In order to do this you need to add two more environmental variables for each Ocelot in the cluster. - -``OCELOT_CERTIFICATE`` - The path to a certificate that can be used to sign the tokens. The certificate needs to be of the type X509 and obviously Ocelot needs to be able to access it. -``OCELOT_CERTIFICATE_PASSWORD`` - 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! - - -Administration API -^^^^^^^^^^^^^^^^^^ - -**POST {adminPath}/connect/token** - -This gets a token for use with the admin area using the client credentials we talk about setting above. Under the hood this calls into an IdentityServer hosted within Ocelot. - -The body of the request is form-data as follows - -``client_id`` set as admin - -``client_secret`` set as whatever you used when setting up the administration services. - -``scope`` set as admin - -``grant_type`` set as client_credentials - -**GET {adminPath}/configuration** - - -This gets the current Ocelot configuration. It is exactly the same JSON we use to set Ocelot up with in the first place. - -**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. - -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. - -**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. - -The region is whatever you set against the Region field in the FileCacheOptions section of the Ocelot configuration. +Administration +============== + +Ocelot supports changing configuration during runtime via an authenticated HTTP API. This can be authenticated in two ways either using Ocelot's +internal IdentityServer (for authenticating requests to the administration API only) or hooking the administration API authentication into your own +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 +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +All you need to do to hook into your own IdentityServer is add the following to your ConfigureServices method. + +.. code-block:: csharp + + public virtual void ConfigureServices(IServiceCollection services) + { + Action options = o => { + // o.Authority = ; + // o.ApiName = ; + // etc.... + }; + + services + .AddOcelot() + .AddAdministration("/administration", options); + } + +You now need to get a token from your IdentityServer and use in subsequent requests to Ocelot's administration API. + +This feature was implemented for `issue 228 `_. It is useful because the IdentityServer authentication +middleware needs the URL of the IdentityServer. If you are using the internal IdentityServer it might not alaways be possible to have the Ocelot URL. + +Internal IdentityServer +^^^^^^^^^^^^^^^^^^^^^^^ + +The API is authenticated using bearer tokens that you request from Ocelot iteself. This is provided by the amazing +`Identity Server `_ project that I have been using for a few years now. Check them out. + +In order to enable the administration section you need to do a few things. First of all add this to your +initial Startup.cs. + +The path can be anything you want and it is obviously reccomended don't use +a url you would like to route through with Ocelot as this will not work. The administration uses the +MapWhen functionality of asp.net core and all requests to {root}/administration will be sent there not +to the Ocelot middleware. + +The secret is the client secret that Ocelot's internal IdentityServer will use to authenticate requests to the administration API. This can be whatever you want it to be! + +.. code-block:: csharp + + public virtual void ConfigureServices(IServiceCollection services) + { + services + .AddOcelot() + .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 +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. + + +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. + +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. + +``OCELOT_CERTIFICATE`` + The path to a certificate that can be used to sign the tokens. The certificate needs to be of the type X509 and obviously Ocelot needs to be able to access it. +``OCELOT_CERTIFICATE_PASSWORD`` + 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 Ocelot instances in the cluster have the same certificate then you are good! + + +Administration API +^^^^^^^^^^^^^^^^^^ + +**POST {adminPath}/connect/token** + +This gets a token for use with the admin area using the client credentials we talk about setting above. Under the hood this calls into an IdentityServer hosted within Ocelot. + +The body of the request is form-data as follows + +``client_id`` set as admin + +``client_secret`` set as whatever you used when setting up the administration services. + +``scope`` set as admin + +``grant_type`` set as client_credentials + +**GET {adminPath}/configuration** + + +This gets the current Ocelot configuration. It is exactly the same JSON we use to set Ocelot up with in the first place. + +**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. + +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. + +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}** + +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. + +The region is whatever you set against the Region field in the FileCacheOptions section of the Ocelot configuration. diff --git a/docs/features/caching.rst b/docs/features/caching.rst index fd780b1e..f7ba7719 100644 --- a/docs/features/caching.rst +++ b/docs/features/caching.rst @@ -6,7 +6,15 @@ the `CacheManager `_ project. This is a that is solving a lot of caching problems. I would reccomend using this package to 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 @@ -16,7 +24,7 @@ The following example shows how to add CacheManager to Ocelot so that you can do 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 @@ -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. -If you look at the example `here `_ you can see how the cache manager is setup and then passed into the Ocelot -AddOcelotOutputCaching configuration method. You can use any settings supported by +If you look at the example `here `_ you can see how the cache manager +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. 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 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, MyCache>()`` + +``IOcelotCache`` this is for output caching. + +``IOcelotCache`` 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.. + diff --git a/docs/features/claimstransformation.rst b/docs/features/claimstransformation.rst index e17e1aa7..ea45f46f 100644 --- a/docs/features/claimstransformation.rst +++ b/docs/features/claimstransformation.rst @@ -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. 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 -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, 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! 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 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 -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 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. -Claims to Claims Tranformation +Claims to Claims Transformation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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] > |" } -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". -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 @@ -68,5 +68,5 @@ Below is an example configuration that will transforms claims to query string pa "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. \ No newline at end of file diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index 220ae2bc..bad1e6c5 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -121,13 +121,18 @@ At the moment there is no validation at this stage it only happens when Ocelot v 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 services .AddOcelot() - .AddStoreOcelotConfigurationInConsul(); + .AddConsul() + .AddConfigStoredInConsul(); 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. @@ -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. +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 ----------------- diff --git a/docs/features/middlewareinjection.rst b/docs/features/middlewareinjection.rst index 44f44fb3..4b663658 100644 --- a/docs/features/middlewareinjection.rst +++ b/docs/features/middlewareinjection.rst @@ -4,8 +4,8 @@ Middleware Injection and Overrides 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! -When setting up Ocelot in your Startup.cs you can provide some additonal middleware -and override middleware. This is done as follos. +When setting up Ocelot in your Startup.cs you can provide some additional middleware +and override middleware. This is done as follows. .. code-block:: csharp @@ -20,7 +20,7 @@ and override middleware. This is done as follos. app.UseOcelot(configuration); 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! 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. -* 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 after as Ocelot does not call the next middleware. diff --git a/docs/features/raft.rst b/docs/features/raft.rst index b193b407..e3793b1f 100644 --- a/docs/features/raft.rst +++ b/docs/features/raft.rst @@ -1,11 +1,15 @@ Raft (EXPERIMENTAL DO NOT USE IN PRODUCTION) ============================================ -Ocelot has recenely integrated `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 `_ which is an implementation of Raft that I have also been working on over the last year. This project is very experimental so please do not use this feature of Ocelot in production until I think it's OK. Raft is a distributed concensus algorythm that allows a cluster of servers (Ocelots) to maintain local state without having a centralised database for storing state (e.g. SQL Server). -In order to enable Rafty in Ocelot you must make the following changes to your Startup.cs. +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 diff --git a/docs/features/routing.rst b/docs/features/routing.rst index e72b5ef8..c27de9a6 100644 --- a/docs/features/routing.rst +++ b/docs/features/routing.rst @@ -1,8 +1,8 @@ Routing ======= -Ocelot's primary functionality is to take incomeing http requests and forward them on -to a downstream service. At the moment in the form of another http request (in the future +Ocelot's primary functionality is to take incoming http requests and forward them on +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). 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 -the following. +To configure a ReRoute you need to add one to the ReRoutes json array. .. code-block:: json @@ -33,16 +32,16 @@ the following. "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 -requests to your downstream services and Ocelot let's you add more than one entry and then select a load balancer. +DownstreamHostAndPorts is a collection that defines the host and port of any downstream services that you wish to forward requests to. +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 -Ocelot can distinguish between requests to the same URL and is obviously needed to work :) +The UpstreamPathTemplate is the URL that Ocelot will use to identity which DownstreamPathTemplate to use for a given request. +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}. -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. +In Ocelot you can add placeholders for variables to your Templates in the form of {something}. +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. @@ -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. -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. .. 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 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 -the ReRoute to be case sensitive is my advice! +evaluation will be case sensitive. 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 @@ -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. -If you do not set UpstreamHost on a ReRoue then any host header can match it. This is basically a catch all and -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. +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. This feature was requested as part of `Issue 216 `_ . Priority ^^^^^^^^ -In `Issue 270 `_ I finally decided to expose the ReRoute priority in -ocelot.json. This means you can decide in what order you want your ReRoutes to match the Upstream HttpRequest. - -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. +You can define the order you want your ReRoutes to match the Upstream HttpRequest by including a "Priority" property in ocelot.json +See `Issue 270 `_ for reference .. code-block:: json @@ -182,14 +181,15 @@ matched /goods/{catchAll} (because this is the first ReRoute in the list!). Dynamic Routing ^^^^^^^^^^^^^^^ -This feature was requested in `issue 340 `_. 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 feature was requested in `issue 340 `_. + +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. 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 @@ -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! -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 @@ -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 -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. \ No newline at end of file +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. \ No newline at end of file diff --git a/docs/features/servicediscovery.rst b/docs/features/servicediscovery.rst index 4fff6a3b..c24a2e39 100644 --- a/docs/features/servicediscovery.rst +++ b/docs/features/servicediscovery.rst @@ -11,6 +11,17 @@ you specify a ServiceName for at ReRoute level. 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 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. +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 `_ 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 --------- @@ -81,7 +117,18 @@ This feature was requested as part of `Issue 262 `_ which is something to do with `Pivotal `_! 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 @@ -111,20 +158,14 @@ is provided by the Pivotal.Discovery.Client NuGet package so big thanks to them Dynamic Routing ^^^^^^^^^^^^^^^ -This feature was requested in `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 segmentof the upstream path to lookup the -downstream service with the service discovery provider. +This feature was requested in `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. 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 -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. +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. -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. +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. -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. +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. 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. diff --git a/docs/features/tracing.rst b/docs/features/tracing.rst index aa4b1fd3..bda43627 100644 --- a/docs/features/tracing.rst +++ b/docs/features/tracing.rst @@ -1,19 +1,29 @@ Tracing ======= -Ocelot providers tracing functionality from the excellent `Butterfly `_ 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 `_ project. The code for the Ocelot integration +can be found `here `_. 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. + ``Install-Package Ocelot.Tracing.Butterfly`` + In your ConfigureServices method .. code-block:: csharp services .AddOcelot() - .AddOpenTracing(option => + // this comes from Ocelot.Tracing.Butterfly package + .AddButterfly(option => { //this is the url that the butterfly collector server is running on... option.CollectorUrl = "http://localhost:9618"; @@ -28,4 +38,4 @@ Then in your ocelot.json add the following to the ReRoute you want to trace.. "UseTracing": true }, -Ocelot will now send tracing information to Butterfly when this ReRoute is called. \ No newline at end of file +Ocelot will now send tracing information to Butterfly when this ReRoute is called. diff --git a/docs/features/websockets.rst b/docs/features/websockets.rst index 624f42a9..5b587919 100644 --- a/docs/features/websockets.rst +++ b/docs/features/websockets.rst @@ -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 proxy these to the upstream client. +SignalR +^^^^^^^ + +Ocelot supports proxying SignalR. This functionality was requested in `Issue 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 ^^^^^^^^^ diff --git a/docs/introduction/notsupported.rst b/docs/introduction/notsupported.rst index e578bca2..3a5ffa42 100644 --- a/docs/introduction/notsupported.rst +++ b/docs/introduction/notsupported.rst @@ -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! -* 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 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 diff --git a/samples/OcelotEureka/ApiGateway/Program.cs b/samples/OcelotEureka/ApiGateway/Program.cs index 28d344c9..d72d8644 100644 --- a/samples/OcelotEureka/ApiGateway/Program.cs +++ b/samples/OcelotEureka/ApiGateway/Program.cs @@ -22,7 +22,7 @@ .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("ocelot.json") + .AddJsonFile("ocelot.json", false, false) .AddEnvironmentVariables(); }) .ConfigureServices(s => diff --git a/samples/OcelotGraphQL/Program.cs b/samples/OcelotGraphQL/Program.cs index 81938f9c..83a48fda 100644 --- a/samples/OcelotGraphQL/Program.cs +++ b/samples/OcelotGraphQL/Program.cs @@ -106,7 +106,7 @@ namespace OcelotGraphQL .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("ocelot.json") + .AddJsonFile("ocelot.json", false, false) .AddEnvironmentVariables(); }) .ConfigureServices(s => { diff --git a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/WebCommunicationListener.cs b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/WebCommunicationListener.cs index 7d913cfa..c1f4a173 100644 --- a/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/WebCommunicationListener.cs +++ b/samples/OcelotServiceFabric/src/OcelotApplicationApiGateway/WebCommunicationListener.cs @@ -59,7 +59,6 @@ namespace OcelotApplicationApiGateway { this.webHost = new WebHostBuilder() .UseKestrel() - //.UseStartup() .UseUrls(this.listeningAddress) .ConfigureAppConfiguration((hostingContext, config) => { @@ -67,7 +66,7 @@ namespace OcelotApplicationApiGateway .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("ocelot.json") + .AddJsonFile("ocelot.json", false, false) .AddEnvironmentVariables(); }) .ConfigureLogging((hostingContext, logging) => diff --git a/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs index 2c70e394..77f9775e 100644 --- a/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs +++ b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs @@ -1,61 +1,61 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authentication; -using Ocelot.Configuration; -using Ocelot.Errors; -using Ocelot.Infrastructure.Extensions; -using Ocelot.Logging; -using Ocelot.Middleware; - -namespace Ocelot.Authentication.Middleware -{ - public class AuthenticationMiddleware : OcelotMiddleware - { - private readonly OcelotRequestDelegate _next; - - public AuthenticationMiddleware(OcelotRequestDelegate next, - IOcelotLoggerFactory loggerFactory) - : base(loggerFactory.CreateLogger()) - { - _next = next; - } - - public async Task Invoke(DownstreamContext context) - { - if (IsAuthenticatedRoute(context.DownstreamReRoute)) - { - Logger.LogInformation($"{context.HttpContext.Request.Path} is an authenticated route. {MiddlewareName} checking if client is authenticated"); - - var result = await context.HttpContext.AuthenticateAsync(context.DownstreamReRoute.AuthenticationOptions.AuthenticationProviderKey); - - context.HttpContext.User = result.Principal; - - if (context.HttpContext.User.Identity.IsAuthenticated) - { - Logger.LogInformation($"Client has been authenticated for {context.HttpContext.Request.Path}"); - await _next.Invoke(context); - } - else - { - var error = new UnauthenticatedError( - $"Request for authenticated route {context.HttpContext.Request.Path} by {context.HttpContext.User.Identity.Name} was unauthenticated"); - - Logger.LogWarning($"Client has NOT been authenticated for {context.HttpContext.Request.Path} and pipeline error set. {error}"); - - SetPipelineError(context, error); - } - } - else - { - Logger.LogInformation($"No authentication needed for {context.HttpContext.Request.Path}"); - - await _next.Invoke(context); - } - } - - private static bool IsAuthenticatedRoute(DownstreamReRoute reRoute) - { - return reRoute.IsAuthenticated; - } - } -} +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Ocelot.Configuration; +using Ocelot.Errors; +using Ocelot.Infrastructure.Extensions; +using Ocelot.Logging; +using Ocelot.Middleware; + +namespace Ocelot.Authentication.Middleware +{ + public class AuthenticationMiddleware : OcelotMiddleware + { + private readonly OcelotRequestDelegate _next; + + public AuthenticationMiddleware(OcelotRequestDelegate next, + IOcelotLoggerFactory loggerFactory) + : base(loggerFactory.CreateLogger()) + { + _next = next; + } + + public async Task Invoke(DownstreamContext context) + { + if (IsAuthenticatedRoute(context.DownstreamReRoute)) + { + Logger.LogInformation($"{context.HttpContext.Request.Path} is an authenticated route. {MiddlewareName} checking if client is authenticated"); + + var result = await context.HttpContext.AuthenticateAsync(context.DownstreamReRoute.AuthenticationOptions.AuthenticationProviderKey); + + context.HttpContext.User = result.Principal; + + if (context.HttpContext.User.Identity.IsAuthenticated) + { + Logger.LogInformation($"Client has been authenticated for {context.HttpContext.Request.Path}"); + await _next.Invoke(context); + } + else + { + var error = new UnauthenticatedError( + $"Request for authenticated route {context.HttpContext.Request.Path} by {context.HttpContext.User.Identity.Name} was unauthenticated"); + + Logger.LogWarning($"Client has NOT been authenticated for {context.HttpContext.Request.Path} and pipeline error set. {error}"); + + SetPipelineError(context, error); + } + } + else + { + Logger.LogInformation($"No authentication needed for {context.HttpContext.Request.Path}"); + + await _next.Invoke(context); + } + } + + private static bool IsAuthenticatedRoute(DownstreamReRoute reRoute) + { + return reRoute.IsAuthenticated; + } + } +} diff --git a/src/Ocelot/Authorisation/ScopesAuthoriser.cs b/src/Ocelot/Authorisation/ScopesAuthoriser.cs index 4b999c10..6d7a8d57 100644 --- a/src/Ocelot/Authorisation/ScopesAuthoriser.cs +++ b/src/Ocelot/Authorisation/ScopesAuthoriser.cs @@ -1,47 +1,47 @@ -using IdentityModel; -using Ocelot.Responses; -using System.Collections.Generic; -using System.Security.Claims; -using System.Linq; - -namespace Ocelot.Authorisation -{ - using Infrastructure.Claims.Parser; - - public class ScopesAuthoriser : IScopesAuthoriser - { - private readonly IClaimsParser _claimsParser; - - public ScopesAuthoriser(IClaimsParser claimsParser) - { - _claimsParser = claimsParser; - } - - public Response Authorise(ClaimsPrincipal claimsPrincipal, List routeAllowedScopes) - { - if (routeAllowedScopes == null || routeAllowedScopes.Count == 0) - { - return new OkResponse(true); - } - - var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, JwtClaimTypes.Scope); - - if (values.IsError) - { - return new ErrorResponse(values.Errors); - } - - var userScopes = values.Data; - - var matchesScopes = routeAllowedScopes.Intersect(userScopes).ToList(); - - if (matchesScopes.Count == 0) - { - return new ErrorResponse( - new ScopeNotAuthorisedError($"no one user scope: '{string.Join(",", userScopes)}' match with some allowed scope: '{string.Join(",", routeAllowedScopes)}'")); - } - - return new OkResponse(true); - } - } -} +using Ocelot.Responses; +using System.Collections.Generic; +using System.Security.Claims; +using System.Linq; + +namespace Ocelot.Authorisation +{ + using Infrastructure.Claims.Parser; + + public class ScopesAuthoriser : IScopesAuthoriser + { + private readonly IClaimsParser _claimsParser; + private readonly string _scope = "scope"; + + public ScopesAuthoriser(IClaimsParser claimsParser) + { + _claimsParser = claimsParser; + } + + public Response Authorise(ClaimsPrincipal claimsPrincipal, List routeAllowedScopes) + { + if (routeAllowedScopes == null || routeAllowedScopes.Count == 0) + { + return new OkResponse(true); + } + + var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, _scope); + + if (values.IsError) + { + return new ErrorResponse(values.Errors); + } + + var userScopes = values.Data; + + var matchesScopes = routeAllowedScopes.Intersect(userScopes).ToList(); + + if (matchesScopes.Count == 0) + { + return new ErrorResponse( + new ScopeNotAuthorisedError($"no one user scope: '{string.Join(",", userScopes)}' match with some allowed scope: '{string.Join(",", routeAllowedScopes)}'")); + } + + return new OkResponse(true); + } + } +} diff --git a/src/Ocelot/Cache/IOcelotCache.cs b/src/Ocelot/Cache/IOcelotCache.cs index b445b647..e70afec1 100644 --- a/src/Ocelot/Cache/IOcelotCache.cs +++ b/src/Ocelot/Cache/IOcelotCache.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; namespace Ocelot.Cache { @@ -9,5 +8,25 @@ namespace Ocelot.Cache void AddAndDelete(string key, T value, TimeSpan ttl, string region); T Get(string key, string region); void ClearRegion(string region); + } + + public class NoCache : IOcelotCache + { + 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); + } } } diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs index 8a27ccba..00c2e322 100644 --- a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs @@ -1,29 +1,25 @@ -using System; -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 +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 { private readonly OcelotRequestDelegate _next; private readonly IOcelotCache _outputCache; - private readonly IRegionCreator _regionCreator; public OutputCacheMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory, - IOcelotCache outputCache, - IRegionCreator regionCreator) + IOcelotCache outputCache) :base(loggerFactory.CreateLogger()) { _next = next; _outputCache = outputCache; - _regionCreator = regionCreator; } public async Task Invoke(DownstreamContext context) diff --git a/src/Ocelot/Cache/OcelotCacheManagerCache.cs b/src/Ocelot/Cache/OcelotCacheManagerCache.cs deleted file mode 100644 index 29f1ed33..00000000 --- a/src/Ocelot/Cache/OcelotCacheManagerCache.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using CacheManager.Core; - -namespace Ocelot.Cache -{ - public class OcelotCacheManagerCache : IOcelotCache - { - private readonly ICacheManager _cacheManager; - - public OcelotCacheManagerCache(ICacheManager cacheManager) - { - _cacheManager = cacheManager; - } - - public void Add(string key, T value, TimeSpan ttl, string region) - { - _cacheManager.Add(new CacheItem(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(key, region); - } - - public void ClearRegion(string region) - { - _cacheManager.ClearRegion(region); - } - } -} \ No newline at end of file diff --git a/src/Ocelot/Cache/Regions.cs b/src/Ocelot/Cache/Regions.cs index baec6087..2ef0f6a8 100644 --- a/src/Ocelot/Cache/Regions.cs +++ b/src/Ocelot/Cache/Regions.cs @@ -1,14 +1,14 @@ -using System.Collections.Generic; - -namespace Ocelot.Cache -{ - public class Regions - { - public Regions(List value) - { - Value = value; - } - - public List Value {get;private set;} - } +namespace Ocelot.Cache +{ + using System.Collections.Generic; + + public class Regions + { + public Regions(List value) + { + Value = value; + } + + public List Value { get; } + } } diff --git a/src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs b/src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs index 69660d83..e1b86d1a 100644 --- a/src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs +++ b/src/Ocelot/Configuration/Builder/QoSOptionsBuilder.cs @@ -6,7 +6,7 @@ private int _durationOfBreak; - private int _timeoutValue; + private int _timeoutValue; private string _key; diff --git a/src/Ocelot/Configuration/CacheOptions.cs b/src/Ocelot/Configuration/CacheOptions.cs index a3a926a4..46c49280 100644 --- a/src/Ocelot/Configuration/CacheOptions.cs +++ b/src/Ocelot/Configuration/CacheOptions.cs @@ -8,7 +8,8 @@ Region = region; } - public int TtlSeconds { get; private set; } - public string Region {get;private set;} + public int TtlSeconds { get; private set; } + + public string Region { get; private set; } } } diff --git a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs index 76934434..89bd7a41 100644 --- a/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs +++ b/src/Ocelot/Configuration/Creator/FileInternalConfigurationCreator.cs @@ -10,6 +10,7 @@ using Ocelot.Configuration.Validator; using Ocelot.DependencyInjection; using Ocelot.Logging; using Ocelot.Responses; +using Microsoft.Extensions.DependencyInjection; namespace Ocelot.Configuration.Creator { @@ -49,14 +50,14 @@ namespace Ocelot.Configuration.Creator IRateLimitOptionsCreator rateLimitOptionsCreator, IRegionCreator regionCreator, IHttpHandlerOptionsCreator httpHandlerOptionsCreator, - IAdministrationPath adminPath, + IServiceProvider serviceProvider, IHeaderFindAndReplaceCreator headerFAndRCreator, IDownstreamAddressesCreator downstreamAddressesCreator ) { _downstreamAddressesCreator = downstreamAddressesCreator; _headerFAndRCreator = headerFAndRCreator; - _adminPath = adminPath; + _adminPath = serviceProvider.GetService(); _regionCreator = regionCreator; _rateLimitOptionsCreator = rateLimitOptionsCreator; _requestIdKeyCreator = requestIdKeyCreator; @@ -103,6 +104,12 @@ namespace Ocelot.Configuration.Creator 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 lbOptions = CreateLoadBalancerOptions(fileConfiguration.GlobalConfiguration.LoadBalancerOptions); @@ -111,8 +118,10 @@ namespace Ocelot.Configuration.Creator var httpHandlerOptions = _httpHandlerOptionsCreator.Create(fileConfiguration.GlobalConfiguration.HttpHandlerOptions); + var adminPath = _adminPath != null ? _adminPath.Path : null; + var config = new InternalConfiguration(reRoutes, - _adminPath.Path, + adminPath, serviceProviderConfiguration, fileConfiguration.GlobalConfiguration.RequestIdKey, lbOptions, @@ -124,7 +133,24 @@ namespace Ocelot.Configuration.Creator return new OkResponse(config); } - public ReRoute SetUpAggregateReRoute(List 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 reRoutes, FileAggregateReRoute aggregateReRoute, FileGlobalConfiguration globalConfiguration) { var applicableReRoutes = reRoutes .SelectMany(x => x.DownstreamReRoute) @@ -186,7 +212,7 @@ namespace Ocelot.Configuration.Creator 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); diff --git a/src/Ocelot/Configuration/Creator/HttpHandlerOptionsCreator.cs b/src/Ocelot/Configuration/Creator/HttpHandlerOptionsCreator.cs index fbe659cf..f55e35ca 100644 --- a/src/Ocelot/Configuration/Creator/HttpHandlerOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/HttpHandlerOptionsCreator.cs @@ -1,24 +1,25 @@ -using Butterfly.Client.Tracing; -using Ocelot.Configuration.File; -using Ocelot.Requester; - -namespace Ocelot.Configuration.Creator -{ - public class HttpHandlerOptionsCreator : IHttpHandlerOptionsCreator - { - private readonly IServiceTracer _tracer; - - public HttpHandlerOptionsCreator(IServiceTracer tracer) - { - _tracer = tracer; - } - - public HttpHandlerOptions Create(FileHttpHandlerOptions options) - { - var useTracing = _tracer.GetType() != typeof(FakeServiceTracer) && options.UseTracing; - - return new HttpHandlerOptions(options.AllowAutoRedirect, - options.UseCookieContainer, useTracing, options.UseProxy); - } - } -} +namespace Ocelot.Configuration.Creator +{ + using System; + using Logging; + using Microsoft.Extensions.DependencyInjection; + using Ocelot.Configuration.File; + + public class HttpHandlerOptionsCreator : IHttpHandlerOptionsCreator + { + private readonly ITracer _tracer; + + public HttpHandlerOptionsCreator(IServiceProvider services) + { + _tracer = services.GetService(); + } + + public HttpHandlerOptions Create(FileHttpHandlerOptions options) + { + var useTracing = _tracer!= null && options.UseTracing; + + return new HttpHandlerOptions(options.AllowAutoRedirect, + options.UseCookieContainer, useTracing, options.UseProxy); + } + } +} diff --git a/src/Ocelot/Configuration/Creator/IRateLimitOptionsCreator.cs b/src/Ocelot/Configuration/Creator/IRateLimitOptionsCreator.cs index 91beea74..e9096786 100644 --- a/src/Ocelot/Configuration/Creator/IRateLimitOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/IRateLimitOptionsCreator.cs @@ -1,9 +1,9 @@ -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public interface IRateLimitOptionsCreator - { - RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting); - } -} \ No newline at end of file +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public interface IRateLimitOptionsCreator + { + RateLimitOptions Create(FileRateLimitRule fileRateLimitRule, FileGlobalConfiguration globalConfiguration); + } +} diff --git a/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs b/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs deleted file mode 100644 index 8569001e..00000000 --- a/src/Ocelot/Configuration/Creator/IdentityServerConfigurationCreator.cs +++ /dev/null @@ -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 { "admin", "openid", "offline_access" }, - credentialsSigningCertificateLocation, - credentialsSigningCertificatePassword - ); - } - } -} diff --git a/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs b/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs index 5d987aee..10e63f6f 100644 --- a/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/RateLimitOptionsCreator.cs @@ -1,32 +1,32 @@ -using System; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public class RateLimitOptionsCreator : IRateLimitOptionsCreator - { - public RateLimitOptions Create(FileReRoute fileReRoute, FileGlobalConfiguration globalConfiguration, bool enableRateLimiting) - { - RateLimitOptions rateLimitOption = null; - - if (enableRateLimiting) - { - rateLimitOption = new RateLimitOptionsBuilder() - .WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader) - .WithClientWhiteList(fileReRoute.RateLimitOptions.ClientWhitelist) - .WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders) - .WithEnableRateLimiting(fileReRoute.RateLimitOptions.EnableRateLimiting) - .WithHttpStatusCode(globalConfiguration.RateLimitOptions.HttpStatusCode) - .WithQuotaExceededMessage(globalConfiguration.RateLimitOptions.QuotaExceededMessage) - .WithRateLimitCounterPrefix(globalConfiguration.RateLimitOptions.RateLimitCounterPrefix) - .WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period, - fileReRoute.RateLimitOptions.PeriodTimespan, - fileReRoute.RateLimitOptions.Limit)) - .Build(); - } - - return rateLimitOption; - } - } -} +using System; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public class RateLimitOptionsCreator : IRateLimitOptionsCreator + { + public RateLimitOptions Create(FileRateLimitRule fileRateLimitRule, FileGlobalConfiguration globalConfiguration) + { + RateLimitOptions rateLimitOption = null; + + if (fileRateLimitRule != null && fileRateLimitRule.EnableRateLimiting) + { + rateLimitOption = new RateLimitOptionsBuilder() + .WithClientIdHeader(globalConfiguration.RateLimitOptions.ClientIdHeader) + .WithClientWhiteList(fileRateLimitRule.ClientWhitelist) + .WithDisableRateLimitHeaders(globalConfiguration.RateLimitOptions.DisableRateLimitHeaders) + .WithEnableRateLimiting(fileRateLimitRule.EnableRateLimiting) + .WithHttpStatusCode(globalConfiguration.RateLimitOptions.HttpStatusCode) + .WithQuotaExceededMessage(globalConfiguration.RateLimitOptions.QuotaExceededMessage) + .WithRateLimitCounterPrefix(globalConfiguration.RateLimitOptions.RateLimitCounterPrefix) + .WithRateLimitRule(new RateLimitRule(fileRateLimitRule.Period, + fileRateLimitRule.PeriodTimespan, + fileRateLimitRule.Limit)) + .Build(); + } + + return rateLimitOption; + } + } +} diff --git a/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs b/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs index c777a50a..3a8d1811 100644 --- a/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs +++ b/src/Ocelot/Configuration/Creator/ReRouteOptionsCreator.cs @@ -1,45 +1,45 @@ -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.File; - -namespace Ocelot.Configuration.Creator -{ - public class ReRouteOptionsCreator : IReRouteOptionsCreator - { - public ReRouteOptions Create(FileReRoute fileReRoute) - { - var isAuthenticated = IsAuthenticated(fileReRoute); - var isAuthorised = IsAuthorised(fileReRoute); - var isCached = IsCached(fileReRoute); - var enableRateLimiting = IsEnableRateLimiting(fileReRoute); - - var options = new ReRouteOptionsBuilder() - .WithIsAuthenticated(isAuthenticated) - .WithIsAuthorised(isAuthorised) - .WithIsCached(isCached) - .WithRateLimiting(enableRateLimiting) - .Build(); - - return options; - } - - private static bool IsEnableRateLimiting(FileReRoute fileReRoute) - { - return (fileReRoute.RateLimitOptions != null && fileReRoute.RateLimitOptions.EnableRateLimiting) ? true : false; - } - - private bool IsAuthenticated(FileReRoute fileReRoute) - { - return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.AuthenticationProviderKey); - } - - private bool IsAuthorised(FileReRoute fileReRoute) - { - return fileReRoute.RouteClaimsRequirement?.Count > 0; - } - - private bool IsCached(FileReRoute fileReRoute) - { - return fileReRoute.FileCacheOptions.TtlSeconds > 0; - } +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.File; + +namespace Ocelot.Configuration.Creator +{ + public class ReRouteOptionsCreator : IReRouteOptionsCreator + { + public ReRouteOptions Create(FileReRoute fileReRoute) + { + var isAuthenticated = IsAuthenticated(fileReRoute); + var isAuthorised = IsAuthorised(fileReRoute); + var isCached = IsCached(fileReRoute); + var enableRateLimiting = IsEnableRateLimiting(fileReRoute); + + var options = new ReRouteOptionsBuilder() + .WithIsAuthenticated(isAuthenticated) + .WithIsAuthorised(isAuthorised) + .WithIsCached(isCached) + .WithRateLimiting(enableRateLimiting) + .Build(); + + return options; + } + + private static bool IsEnableRateLimiting(FileReRoute fileReRoute) + { + return (fileReRoute.RateLimitOptions != null && fileReRoute.RateLimitOptions.EnableRateLimiting) ? true : false; + } + + private bool IsAuthenticated(FileReRoute fileReRoute) + { + return !string.IsNullOrEmpty(fileReRoute.AuthenticationOptions?.AuthenticationProviderKey); + } + + private bool IsAuthorised(FileReRoute fileReRoute) + { + return fileReRoute.RouteClaimsRequirement?.Count > 0; + } + + private bool IsCached(FileReRoute fileReRoute) + { + return fileReRoute.FileCacheOptions.TtlSeconds > 0; + } } -} +} diff --git a/src/Ocelot/Configuration/File/FileConfiguration.cs b/src/Ocelot/Configuration/File/FileConfiguration.cs index e38ae6cb..49eb0c9e 100644 --- a/src/Ocelot/Configuration/File/FileConfiguration.cs +++ b/src/Ocelot/Configuration/File/FileConfiguration.cs @@ -1,20 +1,22 @@ -using System.Collections.Generic; - -namespace Ocelot.Configuration.File -{ - public class FileConfiguration - { - public FileConfiguration() - { - ReRoutes = new List(); - GlobalConfiguration = new FileGlobalConfiguration(); - Aggregates = new List(); - } - - public List ReRoutes { get; set; } - - // Seperate field for aggregates because this let's you re-use ReRoutes in multiple Aggregates - public List Aggregates { get;set; } - public FileGlobalConfiguration GlobalConfiguration { get; set; } - } -} +using System.Collections.Generic; + +namespace Ocelot.Configuration.File +{ + public class FileConfiguration + { + public FileConfiguration() + { + ReRoutes = new List(); + GlobalConfiguration = new FileGlobalConfiguration(); + Aggregates = new List(); + DynamicReRoutes = new List(); + } + + public List ReRoutes { get; set; } + public List DynamicReRoutes { get; set; } + + // Seperate field for aggregates because this let's you re-use ReRoutes in multiple Aggregates + public List Aggregates { get;set; } + public FileGlobalConfiguration GlobalConfiguration { get; set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileDynamicReRoute.cs b/src/Ocelot/Configuration/File/FileDynamicReRoute.cs new file mode 100644 index 00000000..26d8b4d4 --- /dev/null +++ b/src/Ocelot/Configuration/File/FileDynamicReRoute.cs @@ -0,0 +1,8 @@ +namespace Ocelot.Configuration.File +{ + public class FileDynamicReRoute + { + public string ServiceName { get; set; } + public FileRateLimitRule RateLimitRule { get; set; } + } +} diff --git a/src/Ocelot/Configuration/File/FileIdentityServerConfig.cs b/src/Ocelot/Configuration/File/FileIdentityServerConfig.cs deleted file mode 100644 index a07edab1..00000000 --- a/src/Ocelot/Configuration/File/FileIdentityServerConfig.cs +++ /dev/null @@ -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; } - } -} \ No newline at end of file diff --git a/src/Ocelot/Configuration/File/FileRateLimitOptions.cs b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs index c7f99cb0..05c6fac4 100644 --- a/src/Ocelot/Configuration/File/FileRateLimitOptions.cs +++ b/src/Ocelot/Configuration/File/FileRateLimitOptions.cs @@ -1,37 +1,37 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Ocelot.Configuration.File -{ - public class FileRateLimitOptions - { - /// - /// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId - /// - public string ClientIdHeader { get; set; } = "ClientId"; - - /// - /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. - /// If none specified the default will be: - /// API calls quota exceeded! maximum admitted {0} per {1} - /// - public string QuotaExceededMessage { get; set; } - - /// - /// Gets or sets the counter prefix, used to compose the rate limit counter cache key - /// - public string RateLimitCounterPrefix { get; set; } = "ocelot"; - - /// - /// Disables X-Rate-Limit and Rety-After headers - /// - public bool DisableRateLimitHeaders { get; set; } - - /// - /// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests) - /// - public int HttpStatusCode { get; set; } = 429; - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Ocelot.Configuration.File +{ + public class FileRateLimitOptions + { + /// + /// Gets or sets the HTTP header that holds the client identifier, by default is X-ClientId + /// + public string ClientIdHeader { get; set; } = "ClientId"; + + /// + /// Gets or sets a value that will be used as a formatter for the QuotaExceeded response message. + /// If none specified the default will be: + /// API calls quota exceeded! maximum admitted {0} per {1} + /// + public string QuotaExceededMessage { get; set; } + + /// + /// Gets or sets the counter prefix, used to compose the rate limit counter cache key + /// + public string RateLimitCounterPrefix { get; set; } = "ocelot"; + + /// + /// Disables X-Rate-Limit and Rety-After headers + /// + public bool DisableRateLimitHeaders { get; set; } + + /// + /// Gets or sets the HTTP Status code returned when rate limiting occurs, by default value is set to 429 (Too Many Requests) + /// + public int HttpStatusCode { get; set; } = 429; + } +} diff --git a/src/Ocelot/Configuration/File/FileRateLimitRule.cs b/src/Ocelot/Configuration/File/FileRateLimitRule.cs index 7d1ca1ef..6ebdb2bd 100644 --- a/src/Ocelot/Configuration/File/FileRateLimitRule.cs +++ b/src/Ocelot/Configuration/File/FileRateLimitRule.cs @@ -1,52 +1,52 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Ocelot.Infrastructure.Extensions; - -namespace Ocelot.Configuration.File -{ - public class FileRateLimitRule - { - public FileRateLimitRule() - { - ClientWhitelist = new List(); - } - - public List ClientWhitelist { get; set; } - - /// - /// Enables endpoint rate limiting based URL path and HTTP verb - /// - public bool EnableRateLimiting { get; set; } - - /// - /// Rate limit period as in 1s, 1m, 1h - /// - public string Period { get; set; } - - public double PeriodTimespan { get; set; } - - /// - /// Maximum number of requests that a client can make in a defined period - /// - public long Limit { get; set; } - - public override string ToString() - { - if (!EnableRateLimiting) - { - return string.Empty; - } - - var sb = new StringBuilder(); - sb.Append( - $"{nameof(Period)}:{Period},{nameof(PeriodTimespan)}:{PeriodTimespan:F},{nameof(Limit)}:{Limit},{nameof(ClientWhitelist)}:["); - - sb.AppendJoin(',', ClientWhitelist); - sb.Append(']'); - return sb.ToString(); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Ocelot.Infrastructure.Extensions; + +namespace Ocelot.Configuration.File +{ + public class FileRateLimitRule + { + public FileRateLimitRule() + { + ClientWhitelist = new List(); + } + + public List ClientWhitelist { get; set; } + + /// + /// Enables endpoint rate limiting based URL path and HTTP verb + /// + public bool EnableRateLimiting { get; set; } + + /// + /// Rate limit period as in 1s, 1m, 1h + /// + public string Period { get; set; } + + public double PeriodTimespan { get; set; } + + /// + /// Maximum number of requests that a client can make in a defined period + /// + public long Limit { get; set; } + + public override string ToString() + { + if (!EnableRateLimiting) + { + return string.Empty; + } + + var sb = new StringBuilder(); + sb.Append( + $"{nameof(Period)}:{Period},{nameof(PeriodTimespan)}:{PeriodTimespan:F},{nameof(Limit)}:{Limit},{nameof(ClientWhitelist)}:["); + + sb.AppendJoin(',', ClientWhitelist); + sb.Append(']'); + return sb.ToString(); + } + } +} diff --git a/src/Ocelot/Configuration/FileConfigurationController.cs b/src/Ocelot/Configuration/FileConfigurationController.cs index dbc77b77..91777ef3 100644 --- a/src/Ocelot/Configuration/FileConfigurationController.cs +++ b/src/Ocelot/Configuration/FileConfigurationController.cs @@ -4,11 +4,9 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Ocelot.Configuration.File; using Ocelot.Configuration.Setter; -using Ocelot.Raft; namespace Ocelot.Configuration { - using Rafty.Concensus.Node; using Repository; [Authorize] @@ -44,20 +42,6 @@ namespace Ocelot.Configuration { 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)) - { - 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); if (response.IsError) diff --git a/src/Ocelot/Configuration/IIdentityServerConfiguration.cs b/src/Ocelot/Configuration/IIdentityServerConfiguration.cs deleted file mode 100644 index 0eb70347..00000000 --- a/src/Ocelot/Configuration/IIdentityServerConfiguration.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Ocelot.Configuration -{ - using System.Collections.Generic; - - public interface IIdentityServerConfiguration - { - string ApiName { get; } - string ApiSecret { get; } - bool RequireHttps { get; } - List AllowedScopes { get; } - string CredentialsSigningCertificateLocation { get; } - string CredentialsSigningCertificatePassword { get; } - } -} diff --git a/src/Ocelot/Configuration/IdentityServerConfiguration.cs b/src/Ocelot/Configuration/IdentityServerConfiguration.cs deleted file mode 100644 index b8b00ea2..00000000 --- a/src/Ocelot/Configuration/IdentityServerConfiguration.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace Ocelot.Configuration -{ - using System.Collections.Generic; - - public class IdentityServerConfiguration : IIdentityServerConfiguration - { - public IdentityServerConfiguration( - string apiName, - bool requireHttps, - string apiSecret, - List 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 AllowedScopes { get; } - public string ApiSecret { get; } - public string CredentialsSigningCertificateLocation { get; } - public string CredentialsSigningCertificatePassword { get; } - } -} diff --git a/src/Ocelot/Configuration/ReRouteOptions.cs b/src/Ocelot/Configuration/ReRouteOptions.cs index 13875ac8..074e45b6 100644 --- a/src/Ocelot/Configuration/ReRouteOptions.cs +++ b/src/Ocelot/Configuration/ReRouteOptions.cs @@ -1,18 +1,18 @@ -namespace Ocelot.Configuration -{ - public class ReRouteOptions - { - public ReRouteOptions(bool isAuthenticated, bool isAuthorised, bool isCached, bool isEnableRateLimiting) - { - IsAuthenticated = isAuthenticated; - IsAuthorised = isAuthorised; - IsCached = isCached; - EnableRateLimiting = isEnableRateLimiting; - } - - public bool IsAuthenticated { get; private set; } - public bool IsAuthorised { get; private set; } - public bool IsCached { get; private set; } - public bool EnableRateLimiting { get; private set; } - } -} +namespace Ocelot.Configuration +{ + public class ReRouteOptions + { + public ReRouteOptions(bool isAuthenticated, bool isAuthorised, bool isCached, bool isEnableRateLimiting) + { + IsAuthenticated = isAuthenticated; + IsAuthorised = isAuthorised; + IsCached = isCached; + EnableRateLimiting = isEnableRateLimiting; + } + + public bool IsAuthenticated { get; private set; } + public bool IsAuthorised { get; private set; } + public bool IsCached { get; private set; } + public bool EnableRateLimiting { get; private set; } + } +} diff --git a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs deleted file mode 100644 index d8a352a7..00000000 --- a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationRepository.cs +++ /dev/null @@ -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 _cache; - private readonly IOcelotLogger _logger; - - public ConsulFileConfigurationRepository( - Cache.IOcelotCache cache, - IInternalConfigurationRepository repo, - IConsulClientFactory factory, - IOcelotLoggerFactory loggerFactory) - { - _logger = loggerFactory.CreateLogger(); - _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> Get() - { - var config = _cache.Get(_configurationKey, _configurationKey); - - if (config != null) - { - return new OkResponse(config); - } - - var queryResult = await _consul.KV.Get(_configurationKey); - - if (queryResult.Response == null) - { - return new OkResponse(null); - } - - var bytes = queryResult.Response.Value; - - var json = Encoding.UTF8.GetString(bytes); - - var consulConfig = JsonConvert.DeserializeObject(json); - - return new OkResponse(consulConfig); - } - - public async Task 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}")); - } - } -} diff --git a/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs b/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs index a870419c..0141abd7 100644 --- a/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs +++ b/src/Ocelot/Configuration/Repository/DiskFileConfigurationRepository.cs @@ -9,15 +9,16 @@ namespace Ocelot.Configuration.Repository { public class DiskFileConfigurationRepository : IFileConfigurationRepository { - private readonly string _configFilePath; - + private readonly string _environmentFilePath; + private readonly string _ocelotFilePath; private static readonly object _lock = new object(); - private const string ConfigurationFileName = "ocelot"; 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> Get() @@ -26,7 +27,7 @@ namespace Ocelot.Configuration.Repository lock(_lock) { - jsonConfiguration = System.IO.File.ReadAllText(_configFilePath); + jsonConfiguration = System.IO.File.ReadAllText(_environmentFilePath); } var fileConfiguration = JsonConvert.DeserializeObject(jsonConfiguration); @@ -40,12 +41,19 @@ namespace Ocelot.Configuration.Repository 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(new OkResponse()); diff --git a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs b/src/Ocelot/Configuration/Repository/FileConfigurationPoller.cs similarity index 52% rename from src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs rename to src/Ocelot/Configuration/Repository/FileConfigurationPoller.cs index 8dabeaf2..84fc7918 100644 --- a/src/Ocelot/Configuration/Repository/ConsulFileConfigurationPoller.cs +++ b/src/Ocelot/Configuration/Repository/FileConfigurationPoller.cs @@ -1,84 +1,112 @@ -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Setter; -using Ocelot.Logging; - -namespace Ocelot.Configuration.Repository -{ - public class ConsulFileConfigurationPoller : IDisposable - { - private readonly IOcelotLogger _logger; - private readonly IFileConfigurationRepository _repo; - private readonly IFileConfigurationSetter _setter; - private string _previousAsJson; - private readonly Timer _timer; - private bool _polling; - private readonly IConsulPollerConfiguration _config; - - public ConsulFileConfigurationPoller( - IOcelotLoggerFactory factory, - IFileConfigurationRepository repo, - IFileConfigurationSetter setter, - IConsulPollerConfiguration config) - { - _setter = setter; - _config = config; - _logger = factory.CreateLogger(); - _repo = repo; - _previousAsJson = ""; - _timer = new Timer(async x => - { - if(_polling) - { - return; - } - - _polling = true; - await Poll(); - _polling = false; - }, null, _config.Delay, _config.Delay); - } - - private async Task Poll() - { - _logger.LogInformation("Started polling consul"); - - var fileConfig = await _repo.Get(); - - if(fileConfig.IsError) - { - _logger.LogWarning($"error geting file config, errors are {string.Join(",", fileConfig.Errors.Select(x => x.Message))}"); - return; - } - - var asJson = ToJson(fileConfig.Data); - - if(!fileConfig.IsError && asJson != _previousAsJson) - { - await _setter.Set(fileConfig.Data); - _previousAsJson = asJson; - } - - _logger.LogInformation("Finished polling consul"); - } - - /// - /// We could do object comparison here but performance isnt really a problem. This might be an issue one day! - /// - /// hash of the config - private string ToJson(FileConfiguration config) - { - var currentHash = JsonConvert.SerializeObject(config); - return currentHash; - } - - public void Dispose() - { - _timer.Dispose(); - } - } -} +using System; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Newtonsoft.Json; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Setter; +using Ocelot.Logging; + +namespace Ocelot.Configuration.Repository +{ + public class FileConfigurationPoller : IHostedService, IDisposable + { + private readonly IOcelotLogger _logger; + private readonly IFileConfigurationRepository _repo; + private string _previousAsJson; + private Timer _timer; + private bool _polling; + private readonly IFileConfigurationPollerOptions _options; + private readonly IInternalConfigurationRepository _internalConfigRepo; + private readonly IInternalConfigurationCreator _internalConfigCreator; + + public FileConfigurationPoller( + IOcelotLoggerFactory factory, + IFileConfigurationRepository repo, + IFileConfigurationPollerOptions options, + IInternalConfigurationRepository internalConfigRepo, + IInternalConfigurationCreator internalConfigCreator) + { + _internalConfigRepo = internalConfigRepo; + _internalConfigCreator = internalConfigCreator; + _options = options; + _logger = factory.CreateLogger(); + _repo = repo; + _previousAsJson = ""; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _logger.LogInformation($"{nameof(FileConfigurationPoller)} is starting."); + + _timer = new Timer(async x => + { + if(_polling) + { + return; + } + + _polling = true; + await Poll(); + _polling = false; + }, 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() + { + _logger.LogInformation("Started polling"); + + var fileConfig = await _repo.Get(); + + if(fileConfig.IsError) + { + _logger.LogWarning($"error geting file config, errors are {string.Join(",", fileConfig.Errors.Select(x => x.Message))}"); + return; + } + + var asJson = ToJson(fileConfig.Data); + + if(!fileConfig.IsError && asJson != _previousAsJson) + { + var config = await _internalConfigCreator.Create(fileConfig.Data); + + if(!config.IsError) + { + _internalConfigRepo.AddOrReplace(config.Data); + } + + _previousAsJson = asJson; + } + + _logger.LogInformation("Finished polling"); + } + + /// + /// We could do object comparison here but performance isnt really a problem. This might be an issue one day! + /// + /// hash of the config + private string ToJson(FileConfiguration config) + { + var currentHash = JsonConvert.SerializeObject(config); + return currentHash; + } + + public void Dispose() + { + _timer.Dispose(); + } + } +} diff --git a/src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs b/src/Ocelot/Configuration/Repository/IFileConfigurationPollerOptions.cs similarity index 62% rename from src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs rename to src/Ocelot/Configuration/Repository/IFileConfigurationPollerOptions.cs index d1f1430d..70fc9bd4 100644 --- a/src/Ocelot/Configuration/Repository/IConsulPollerConfiguration.cs +++ b/src/Ocelot/Configuration/Repository/IFileConfigurationPollerOptions.cs @@ -1,6 +1,6 @@ namespace Ocelot.Configuration.Repository { - public interface IConsulPollerConfiguration + public interface IFileConfigurationPollerOptions { int Delay { get; } } diff --git a/src/Ocelot/Configuration/Repository/InMemoryConsulPollerConfiguration.cs b/src/Ocelot/Configuration/Repository/InMemoryFileConfigurationPollerOptions.cs similarity index 50% rename from src/Ocelot/Configuration/Repository/InMemoryConsulPollerConfiguration.cs rename to src/Ocelot/Configuration/Repository/InMemoryFileConfigurationPollerOptions.cs index 9e411f76..f5ebe186 100644 --- a/src/Ocelot/Configuration/Repository/InMemoryConsulPollerConfiguration.cs +++ b/src/Ocelot/Configuration/Repository/InMemoryFileConfigurationPollerOptions.cs @@ -1,6 +1,6 @@ namespace Ocelot.Configuration.Repository { - public class InMemoryConsulPollerConfiguration : IConsulPollerConfiguration + public class InMemoryFileConfigurationPollerOptions : IFileConfigurationPollerOptions { public int Delay => 1000; } diff --git a/src/Ocelot/Configuration/Repository/UnableToSetConfigInConsulError.cs b/src/Ocelot/Configuration/Repository/UnableToSetConfigInConsulError.cs deleted file mode 100644 index 52b5c372..00000000 --- a/src/Ocelot/Configuration/Repository/UnableToSetConfigInConsulError.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Ocelot.Errors; - -namespace Ocelot.Configuration.Repository -{ - public class UnableToSetConfigInConsulError : Error - { - public UnableToSetConfigInConsulError(string message) - : base(message, OcelotErrorCode.UnableToSetConfigInConsulError) - { - } - } -} \ No newline at end of file diff --git a/src/Ocelot/Configuration/Setter/FileAndInternalConfigurationSetter.cs b/src/Ocelot/Configuration/Setter/FileAndInternalConfigurationSetter.cs index 6821553e..a5549b29 100644 --- a/src/Ocelot/Configuration/Setter/FileAndInternalConfigurationSetter.cs +++ b/src/Ocelot/Configuration/Setter/FileAndInternalConfigurationSetter.cs @@ -8,7 +8,7 @@ namespace Ocelot.Configuration.Setter { public class FileAndInternalConfigurationSetter : IFileConfigurationSetter { - private readonly IInternalConfigurationRepository _configRepo; + private readonly IInternalConfigurationRepository internalConfigRepo; private readonly IInternalConfigurationCreator _configCreator; private readonly IFileConfigurationRepository _repo; @@ -17,7 +17,7 @@ namespace Ocelot.Configuration.Setter IInternalConfigurationCreator configCreator, IFileConfigurationRepository repo) { - _configRepo = configRepo; + internalConfigRepo = configRepo; _configCreator = configCreator; _repo = repo; } @@ -35,7 +35,7 @@ namespace Ocelot.Configuration.Setter if(!config.IsError) { - _configRepo.AddOrReplace(config.Data); + internalConfigRepo.AddOrReplace(config.Data); } return new ErrorResponse(config.Errors); diff --git a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs index 6c6e2b48..af6351ea 100644 --- a/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs +++ b/src/Ocelot/DependencyInjection/ConfigurationBuilderExtensions.cs @@ -30,6 +30,11 @@ namespace Ocelot.DependencyInjection } 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"; @@ -41,7 +46,7 @@ namespace Ocelot.DependencyInjection var reg = new Regex(subConfigPattern, RegexOptions.IgnoreCase | RegexOptions.Singleline); - var files = new DirectoryInfo(".") + var files = new DirectoryInfo(folder) .EnumerateFiles() .Where(fi => reg.IsMatch(fi.Name) && (fi.Name != excludeConfigName)) .ToList(); @@ -72,7 +77,7 @@ namespace Ocelot.DependencyInjection File.WriteAllText(primaryConfigFile, json); - builder.AddJsonFile(primaryConfigFile); + builder.AddJsonFile(primaryConfigFile, false, false); return builder; } diff --git a/src/Ocelot/DependencyInjection/IOcelotAdministrationBuilder.cs b/src/Ocelot/DependencyInjection/IOcelotAdministrationBuilder.cs index 859f4d98..bcb01189 100644 --- a/src/Ocelot/DependencyInjection/IOcelotAdministrationBuilder.cs +++ b/src/Ocelot/DependencyInjection/IOcelotAdministrationBuilder.cs @@ -1,7 +1,11 @@ namespace Ocelot.DependencyInjection { + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + public interface IOcelotAdministrationBuilder { - IOcelotAdministrationBuilder AddRafty(); + IServiceCollection Services { get; } + IConfiguration ConfigurationRoot { get; } } } diff --git a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs index 20eafd36..5e22f8f6 100644 --- a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs @@ -1,29 +1,23 @@ -using Butterfly.Client.AspNetCore; -using CacheManager.Core; using System; using System.Net.Http; -using IdentityServer4.AccessTokenValidation; using Ocelot.Middleware.Multiplexer; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Configuration; namespace Ocelot.DependencyInjection { public interface IOcelotBuilder { - IOcelotBuilder AddStoreOcelotConfigurationInConsul(); + IServiceCollection Services { get; } - IOcelotBuilder AddCacheManager(Action settings); - - IOcelotBuilder AddOpenTracing(Action settings); - - IOcelotAdministrationBuilder AddAdministration(string path, string secret); - - IOcelotAdministrationBuilder AddAdministration(string path, Action configOptions); + IConfiguration Configuration { get; } IOcelotBuilder AddDelegatingHandler(bool global = false) where T : DelegatingHandler; IOcelotBuilder AddSingletonDefinedAggregator() where T : class, IDefinedAggregator; + IOcelotBuilder AddTransientDefinedAggregator() where T : class, IDefinedAggregator; } diff --git a/src/Ocelot/DependencyInjection/NullAdministrationPath.cs b/src/Ocelot/DependencyInjection/NullAdministrationPath.cs deleted file mode 100644 index 7ef626b7..00000000 --- a/src/Ocelot/DependencyInjection/NullAdministrationPath.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Ocelot.DependencyInjection -{ - public class NullAdministrationPath : IAdministrationPath - { - public NullAdministrationPath() - { - Path = null; - } - - public string Path {get;private set;} - } -} diff --git a/src/Ocelot/DependencyInjection/OcelotAdministrationBuilder.cs b/src/Ocelot/DependencyInjection/OcelotAdministrationBuilder.cs index c96cdef3..6e59284f 100644 --- a/src/Ocelot/DependencyInjection/OcelotAdministrationBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotAdministrationBuilder.cs @@ -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 { - using Rafty.Concensus.Node; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; public class OcelotAdministrationBuilder : IOcelotAdministrationBuilder { - private readonly IServiceCollection _services; - private readonly IConfiguration _configurationRoot; - + public IServiceCollection Services { get; } + public IConfiguration ConfigurationRoot { get; } + public OcelotAdministrationBuilder(IServiceCollection services, IConfiguration configurationRoot) { - _configurationRoot = configurationRoot; - _services = services; - } - - public IOcelotAdministrationBuilder AddRafty() - { - var settings = new InMemorySettings(4000, 6000, 100, 10000); - _services.AddSingleton(); - _services.AddSingleton(); - _services.AddSingleton(settings); - _services.AddSingleton(); - _services.AddSingleton(); - _services.Configure(_configurationRoot); - return this; + ConfigurationRoot = configurationRoot; + Services = services; } } } diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 1b93ddbb..80c8ae29 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -1,7 +1,5 @@ namespace Ocelot.DependencyInjection { - using CacheManager.Core; - using IdentityServer4.Models; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -32,183 +30,125 @@ namespace Ocelot.DependencyInjection using Ocelot.ServiceDiscovery; using System; using System.Collections.Generic; - using System.IdentityModel.Tokens.Jwt; using System.Reflection; using System.Security.Cryptography.X509Certificates; - using IdentityServer4.AccessTokenValidation; using Microsoft.AspNetCore.Builder; using Ocelot.Configuration; using Microsoft.Extensions.DependencyInjection.Extensions; using System.Net.Http; - using Butterfly.Client.AspNetCore; using Ocelot.Infrastructure; - using Ocelot.Infrastructure.Consul; - using Butterfly.Client.Tracing; using Ocelot.Middleware.Multiplexer; using ServiceDiscovery.Providers; - using Steeltoe.Common.Discovery; - using Pivotal.Discovery.Client; using Ocelot.Request.Creator; public class OcelotBuilder : IOcelotBuilder { - private readonly IServiceCollection _services; - private readonly IConfiguration _configurationRoot; + public IServiceCollection Services { get; } + public IConfiguration Configuration { get; } public OcelotBuilder(IServiceCollection services, IConfiguration configurationRoot) { - _configurationRoot = configurationRoot; - _services = services; - - //add default cache settings... - Action defaultCachingSettings = x => - { - x.WithDictionaryHandle(); - }; + Configuration = configurationRoot; + Services = services; - AddCacheManager(defaultCachingSettings); + Services.Configure(configurationRoot); + + //default no caches... + Services.TryAddSingleton, NoCache>(); + Services.TryAddSingleton, NoCache>(); - //add ocelot services... - _services.Configure(configurationRoot); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.AddSingleton(); - _services.AddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - - if (UsingEurekaServiceDiscoveryProvider(configurationRoot)) - { - _services.AddDiscoveryClient(configurationRoot); - } - else - { - _services.TryAddSingleton(); - } - - _services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.AddSingleton(); + Services.AddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); // 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 - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.AddMemoryCache(); - _services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.AddMemoryCache(); + Services.TryAddSingleton(); //add asp.net services.. var assembly = typeof(FileConfigurationController).GetTypeInfo().Assembly; - _services.AddMvcCore() + Services.AddMvcCore() .AddApplicationPart(assembly) .AddControllersAsServices() .AddAuthorization() .AddJsonFormatters(); - _services.AddLogging(); - _services.AddMiddlewareAnalysis(); - _services.AddWebEncoders(); - _services.AddSingleton(new NullAdministrationPath()); + Services.AddLogging(); + Services.AddMiddlewareAnalysis(); + Services.AddWebEncoders(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.AddSingleton(); - - // We add this here so that we can always inject something into the factory for IoC.. - _services.AddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - _services.TryAddSingleton(); - } - - 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 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); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.AddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); + Services.TryAddSingleton(); } public IOcelotBuilder AddSingletonDefinedAggregator() where T : class, IDefinedAggregator { - _services.AddSingleton(); + Services.AddSingleton(); return this; } public IOcelotBuilder AddTransientDefinedAggregator() where T : class, IDefinedAggregator { - _services.AddTransient(); + Services.AddTransient(); return this; } @@ -217,142 +157,18 @@ namespace Ocelot.DependencyInjection { if(global) { - _services.AddTransient(); - _services.AddTransient(s => { + Services.AddTransient(); + Services.AddTransient(s => { var service = s.GetService(); return new GlobalDelegatingHandler(service); }); } else { - _services.AddTransient(); + Services.AddTransient(); } return this; } - - public IOcelotBuilder AddOpenTracing(Action settings) - { - // Earlier we add FakeServiceTracer and need to remove it here before we add butterfly - _services.RemoveAll(); - _services.AddButterfly(settings); - return this; - } - - public IOcelotBuilder AddStoreOcelotConfigurationInConsul() - { - _services.AddSingleton(); - _services.AddSingleton(); - return this; - } - - public IOcelotBuilder AddCacheManager(Action settings) - { - var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", settings); - var ocelotOutputCacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache); - - _services.RemoveAll(typeof(ICacheManager)); - _services.RemoveAll(typeof(IOcelotCache)); - _services.AddSingleton>(cacheManagerOutputCache); - _services.AddSingleton>(ocelotOutputCacheManager); - - var ocelotConfigCacheManagerOutputCache = CacheFactory.Build("OcelotConfigurationCache", settings); - var ocelotConfigCacheManager = new OcelotCacheManagerCache(ocelotConfigCacheManagerOutputCache); - _services.RemoveAll(typeof(ICacheManager)); - _services.RemoveAll(typeof(IOcelotCache)); - _services.AddSingleton>(ocelotConfigCacheManagerOutputCache); - _services.AddSingleton>(ocelotConfigCacheManager); - - var fileConfigCacheManagerOutputCache = CacheFactory.Build("FileConfigurationCache", settings); - var fileConfigCacheManager = new OcelotCacheManagerCache(fileConfigCacheManagerOutputCache); - _services.RemoveAll(typeof(ICacheManager)); - _services.RemoveAll(typeof(IOcelotCache)); - _services.AddSingleton>(fileConfigCacheManagerOutputCache); - _services.AddSingleton>(fileConfigCacheManager); - return this; - } - - private void AddIdentityServer(Action configOptions) - { - _services - .AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme) - .AddIdentityServerAuthentication(configOptions); - } - - private void AddIdentityServer(IIdentityServerConfiguration identityServerConfiguration, IAdministrationPath adminPath) - { - _services.TryAddSingleton(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 Resources(IIdentityServerConfiguration identityServerConfiguration) - { - return new List - { - new ApiResource(identityServerConfiguration.ApiName, identityServerConfiguration.ApiName) - { - ApiSecrets = new List - { - new Secret - { - Value = identityServerConfiguration.ApiSecret.Sha256() - } - } - }, - }; - } - - private List Client(IIdentityServerConfiguration identityServerConfiguration) - { - return new List - { - new Client - { - ClientId = identityServerConfiguration.ApiName, - AllowedGrantTypes = GrantTypes.ClientCredentials, - ClientSecrets = new List {new Secret(identityServerConfiguration.ApiSecret.Sha256())}, - AllowedScopes = { identityServerConfiguration.ApiName } - } - }; - } - - private static bool UsingEurekaServiceDiscoveryProvider(IConfiguration configurationRoot) - { - var type = configurationRoot.GetValue("GlobalConfiguration:ServiceDiscoveryProvider:Type", - string.Empty); - - return type.ToLower() == "eureka"; - } } } diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs index 9a90edaf..f7dea902 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteCreator.cs @@ -2,6 +2,7 @@ { using System.Collections.Concurrent; using System.Collections.Generic; + using System.Linq; using Configuration; using Configuration.Builder; using Configuration.Creator; @@ -43,7 +44,7 @@ var qosOptions = _qoSOptionsCreator.Create(configuration.QoSOptions, downstreamPathForKeys, new []{ upstreamHttpMethod }); - var downstreamReRoute = new DownstreamReRouteBuilder() + var downstreamReRouteBuilder = new DownstreamReRouteBuilder() .WithServiceName(serviceName) .WithLoadBalancerKey(loadBalancerKey) .WithDownstreamPathTemplate(downstreamPath) @@ -51,8 +52,22 @@ .WithHttpHandlerOptions(configuration.HttpHandlerOptions) .WithQosOptions(qosOptions) .WithDownstreamScheme(configuration.DownstreamScheme) - .WithLoadBalancerOptions(configuration.LoadBalancerOptions) - .Build(); + .WithLoadBalancerOptions(configuration.LoadBalancerOptions); + + 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() .WithDownstreamReRoute(downstreamReRoute) diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteProviderFactory.cs b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteProviderFactory.cs index fc74f679..080172d5 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteProviderFactory.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/DownstreamRouteProviderFactory.cs @@ -20,7 +20,9 @@ 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"); return _providers[nameof(DownstreamRouteCreator)]; diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index b6c4bcf6..3916721d 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -52,6 +52,7 @@ namespace Ocelot.DownstreamRouteFinder.Middleware } var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value)); + Logger.LogDebug($"downstream templates are {downstreamPathTemplates}"); context.TemplatePlaceholderNameAndValues = downstreamRoute.Data.TemplatePlaceholderNameAndValues; diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index 6057e58a..855efa34 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -52,7 +52,15 @@ namespace Ocelot.DownstreamUrlCreator.Middleware if(ContainsQueryString(dsPath)) { context.DownstreamRequest.AbsolutePath = GetPath(dsPath); - context.DownstreamRequest.Query = GetQueryString(dsPath); + + if (string.IsNullOrEmpty(context.DownstreamRequest.Query)) + { + context.DownstreamRequest.Query = GetQueryString(dsPath); + } + else + { + context.DownstreamRequest.Query += GetQueryString(dsPath).Replace('?', '&'); + } } else { diff --git a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs index b7e51ef9..58c18901 100644 --- a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs +++ b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs @@ -1,104 +1,103 @@ -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 -{ - using Configuration; - - /// - /// Catches all unhandled exceptions thrown by middleware, logs and returns a 500 - /// - public class ExceptionHandlerMiddleware : OcelotMiddleware - { - private readonly OcelotRequestDelegate _next; - private readonly IInternalConfigurationRepository _configRepo; - private readonly IRequestScopedDataRepository _repo; - - public ExceptionHandlerMiddleware(OcelotRequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IInternalConfigurationRepository configRepo, - IRequestScopedDataRepository repo) - : base(loggerFactory.CreateLogger()) - { - _configRepo = configRepo; - _repo = repo; - _next = next; - } - - public async Task Invoke(DownstreamContext context) - { - try - { - //try and get the global request id and set it for logs... - //should this basically be immutable per request...i guess it should! - //first thing is get config - var configuration = _configRepo.Get(); - - if (configuration.IsError) - { - throw new Exception($"{MiddlewareName} setting pipeline errors. IOcelotConfigurationProvider returned {configuration.Errors.ToErrorString()}"); - } +namespace Ocelot.Errors.Middleware +{ + 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; + + /// + /// Catches all unhandled exceptions thrown by middleware, logs and returns a 500 + /// + public class ExceptionHandlerMiddleware : OcelotMiddleware + { + private readonly OcelotRequestDelegate _next; + private readonly IInternalConfigurationRepository _configRepo; + private readonly IRequestScopedDataRepository _repo; - TrySetGlobalRequestId(context, configuration.Data); - - context.Configuration = configuration.Data; - - Logger.LogDebug("ocelot pipeline started"); - - await _next.Invoke(context); - } - catch (Exception e) - { - Logger.LogDebug("error calling middleware"); - - var message = CreateMessage(context, e); - - Logger.LogError(message, e); - - SetInternalServerErrorOnResponse(context); - } - - Logger.LogDebug("ocelot pipeline finished"); - } - - private void TrySetGlobalRequestId(DownstreamContext context, IInternalConfiguration configuration) - { - var key = configuration.RequestId; - - if (!string.IsNullOrEmpty(key) && context.HttpContext.Request.Headers.TryGetValue(key, out var upstreamRequestIds)) - { - context.HttpContext.TraceIdentifier = upstreamRequestIds.First(); - } - - _repo.Add("RequestId", context.HttpContext.TraceIdentifier); - } - - private void SetInternalServerErrorOnResponse(DownstreamContext context) - { - if (!context.HttpContext.Response.HasStarted) - { - context.HttpContext.Response.StatusCode = 500; - } - } - - private string CreateMessage(DownstreamContext context, Exception e) - { - var message = - $"Exception caught in global error handler, exception message: {e.Message}, exception stack: {e.StackTrace}"; - - if (e.InnerException != null) - { - message = - $"{message}, inner exception message {e.InnerException.Message}, inner exception stack {e.InnerException.StackTrace}"; - } - - return $"{message} RequestId: {context.HttpContext.TraceIdentifier}"; - } - } -} + public ExceptionHandlerMiddleware(OcelotRequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IInternalConfigurationRepository configRepo, + IRequestScopedDataRepository repo) + : base(loggerFactory.CreateLogger()) + { + _configRepo = configRepo; + _repo = repo; + _next = next; + } + + public async Task Invoke(DownstreamContext context) + { + try + { + //try and get the global request id and set it for logs... + //should this basically be immutable per request...i guess it should! + //first thing is get config + var configuration = _configRepo.Get(); + + if (configuration.IsError) + { + throw new Exception($"{MiddlewareName} setting pipeline errors. IOcelotConfigurationProvider returned {configuration.Errors.ToErrorString()}"); + } + + TrySetGlobalRequestId(context, configuration.Data); + + context.Configuration = configuration.Data; + + Logger.LogDebug("ocelot pipeline started"); + + await _next.Invoke(context); + } + catch (Exception e) + { + Logger.LogDebug("error calling middleware"); + + var message = CreateMessage(context, e); + + Logger.LogError(message, e); + + SetInternalServerErrorOnResponse(context); + } + + Logger.LogDebug("ocelot pipeline finished"); + } + + private void TrySetGlobalRequestId(DownstreamContext context, IInternalConfiguration configuration) + { + var key = configuration.RequestId; + + if (!string.IsNullOrEmpty(key) && context.HttpContext.Request.Headers.TryGetValue(key, out var upstreamRequestIds)) + { + context.HttpContext.TraceIdentifier = upstreamRequestIds.First(); + } + + _repo.Add("RequestId", context.HttpContext.TraceIdentifier); + } + + private void SetInternalServerErrorOnResponse(DownstreamContext context) + { + if (!context.HttpContext.Response.HasStarted) + { + context.HttpContext.Response.StatusCode = 500; + } + } + + private string CreateMessage(DownstreamContext context, Exception e) + { + var message = + $"Exception caught in global error handler, exception message: {e.Message}, exception stack: {e.StackTrace}"; + + if (e.InnerException != null) + { + message = + $"{message}, inner exception message {e.InnerException.Message}, inner exception stack {e.InnerException.StackTrace}"; + } + + return $"{message} RequestId: {context.HttpContext.TraceIdentifier}"; + } + } +} diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index 8109bb52..7a2dd030 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -29,7 +29,6 @@ UnableToFindLoadBalancerError, RequestTimedOutError, UnableToFindQoSProviderError, - UnableToSetConfigInConsulError, UnmappableRequestError, RateLimitOptionsError, PathTemplateDoesntStartWithForwardSlash, diff --git a/src/Ocelot/Infrastructure/Consul/ConsulClientFactory.cs b/src/Ocelot/Infrastructure/Consul/ConsulClientFactory.cs deleted file mode 100644 index 26799934..00000000 --- a/src/Ocelot/Infrastructure/Consul/ConsulClientFactory.cs +++ /dev/null @@ -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; - } - }); - } - } -} diff --git a/src/Ocelot/Infrastructure/Consul/IConsulClientFactory.cs b/src/Ocelot/Infrastructure/Consul/IConsulClientFactory.cs deleted file mode 100644 index 43428686..00000000 --- a/src/Ocelot/Infrastructure/Consul/IConsulClientFactory.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Consul; -using Ocelot.ServiceDiscovery.Configuration; - -namespace Ocelot.Infrastructure.Consul -{ - public interface IConsulClientFactory - { - IConsulClient Get(ConsulRegistryConfiguration config); - } -} diff --git a/src/Ocelot/Infrastructure/Extensions/StringValuesExtensions.cs b/src/Ocelot/Infrastructure/Extensions/StringValuesExtensions.cs index 3c85ce5c..df9d24da 100644 --- a/src/Ocelot/Infrastructure/Extensions/StringValuesExtensions.cs +++ b/src/Ocelot/Infrastructure/Extensions/StringValuesExtensions.cs @@ -3,7 +3,7 @@ using System.Linq; namespace Ocelot.Infrastructure.Extensions { - internal static class StringValuesExtensions + public static class StringValuesExtensions { public static string GetValue(this StringValues stringValues) { diff --git a/src/Ocelot/Logging/ITracer.cs b/src/Ocelot/Logging/ITracer.cs new file mode 100644 index 00000000..9e035d75 --- /dev/null +++ b/src/Ocelot/Logging/ITracer.cs @@ -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 SendAsync( + HttpRequestMessage request, + CancellationToken cancellationToken, + Action addTraceIdToRepo, + Func> baseSendAsync); + } +} diff --git a/src/Ocelot/Logging/OcelotDiagnosticListener.cs b/src/Ocelot/Logging/OcelotDiagnosticListener.cs index f2c51760..48c1a35c 100644 --- a/src/Ocelot/Logging/OcelotDiagnosticListener.cs +++ b/src/Ocelot/Logging/OcelotDiagnosticListener.cs @@ -1,96 +1,67 @@ -using System; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DiagnosticAdapter; -using Butterfly.Client.AspNetCore; -using Butterfly.OpenTracing; -using Ocelot.Middleware; -using Butterfly.Client.Tracing; -using System.Linq; -using System.Collections.Generic; -using Ocelot.Infrastructure.Extensions; -using Ocelot.Requester; - -namespace Ocelot.Logging -{ - public class OcelotDiagnosticListener - { - private readonly IServiceTracer _tracer; - private readonly IOcelotLogger _logger; - - public OcelotDiagnosticListener(IOcelotLoggerFactory factory, IServiceTracer tracer) - { - _tracer = tracer; - _logger = factory.CreateLogger(); - } - - [DiagnosticName("Ocelot.MiddlewareException")] - public virtual void OcelotMiddlewareException(Exception exception, DownstreamContext context, string name) - { - _logger.LogTrace($"Ocelot.MiddlewareException: {name}; {exception.Message};"); - Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}"); - } - - [DiagnosticName("Ocelot.MiddlewareStarted")] - public virtual void OcelotMiddlewareStarted(DownstreamContext context, string name) - { - _logger.LogTrace($"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}"); - Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}"); - } - - [DiagnosticName("Ocelot.MiddlewareFinished")] - public virtual void OcelotMiddlewareFinished(DownstreamContext context, string name) - { - _logger.LogTrace($"Ocelot.MiddlewareFinished: {name}; {context.HttpContext.Request.Path}"); - Event(context.HttpContext, $"OcelotMiddlewareFinished: {name}; {context.HttpContext.Request.Path}"); - } - - [DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareStarting")] - public virtual void OnMiddlewareStarting(HttpContext httpContext, string name) - { - _logger.LogTrace($"MiddlewareStarting: {name}; {httpContext.Request.Path}"); - Event(httpContext, $"MiddlewareStarting: {name}; {httpContext.Request.Path}"); - } - - [DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareException")] - public virtual void OnMiddlewareException(Exception exception, string name) - { - _logger.LogTrace($"MiddlewareException: {name}; {exception.Message};"); - } - - [DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareFinished")] - public virtual void OnMiddlewareFinished(HttpContext httpContext, string name) - { - _logger.LogTrace($"MiddlewareFinished: {name}; {httpContext.Response.StatusCode}"); - Event(httpContext, $"MiddlewareFinished: {name}; {httpContext.Response.StatusCode}"); - } - - private void Event(HttpContext httpContext, string @event) - { - // todo - if the user isnt using tracing the code gets here and will blow up on - // _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(x.Key, x.Value.GetValue())).GetEnumerator())) - { - spanBuilder.AsChildOf(spanContext); - } - - span = _tracer.Start(spanBuilder); - httpContext.SetSpan(span); - } - - span?.Log(LogField.CreateNew().Event(@event)); - } - } -} +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DiagnosticAdapter; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Middleware; + +namespace Ocelot.Logging +{ + public class OcelotDiagnosticListener + { + private readonly IOcelotLogger _logger; + private readonly ITracer _tracer; + + public OcelotDiagnosticListener(IOcelotLoggerFactory factory, IServiceProvider serviceProvider) + { + _logger = factory.CreateLogger(); + _tracer = serviceProvider.GetService(); + + } + + [DiagnosticName("Ocelot.MiddlewareException")] + public virtual void OcelotMiddlewareException(Exception exception, DownstreamContext context, string name) + { + _logger.LogTrace($"Ocelot.MiddlewareException: {name}; {exception.Message};"); + Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}"); + } + + [DiagnosticName("Ocelot.MiddlewareStarted")] + public virtual void OcelotMiddlewareStarted(DownstreamContext context, string name) + { + _logger.LogTrace($"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}"); + Event(context.HttpContext, $"Ocelot.MiddlewareStarted: {name}; {context.HttpContext.Request.Path}"); + } + + [DiagnosticName("Ocelot.MiddlewareFinished")] + public virtual void OcelotMiddlewareFinished(DownstreamContext context, string name) + { + _logger.LogTrace($"Ocelot.MiddlewareFinished: {name}; {context.HttpContext.Request.Path}"); + Event(context.HttpContext, $"OcelotMiddlewareFinished: {name}; {context.HttpContext.Request.Path}"); + } + + [DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareStarting")] + public virtual void OnMiddlewareStarting(HttpContext httpContext, string name) + { + _logger.LogTrace($"MiddlewareStarting: {name}; {httpContext.Request.Path}"); + Event(httpContext, $"MiddlewareStarting: {name}; {httpContext.Request.Path}"); + } + + [DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareException")] + public virtual void OnMiddlewareException(Exception exception, string name) + { + _logger.LogTrace($"MiddlewareException: {name}; {exception.Message};"); + } + + [DiagnosticName("Microsoft.AspNetCore.MiddlewareAnalysis.MiddlewareFinished")] + public virtual void OnMiddlewareFinished(HttpContext httpContext, string name) + { + _logger.LogTrace($"MiddlewareFinished: {name}; {httpContext.Response.StatusCode}"); + Event(httpContext, $"MiddlewareFinished: {name}; {httpContext.Response.StatusCode}"); + } + + private void Event(HttpContext httpContext, string @event) + { + _tracer?.Event(httpContext, @event); + } + } +} diff --git a/src/Ocelot/Middleware/OcelotMiddlewareConfigurationDelegate.cs b/src/Ocelot/Middleware/OcelotMiddlewareConfigurationDelegate.cs new file mode 100644 index 00000000..f0dbb5c1 --- /dev/null +++ b/src/Ocelot/Middleware/OcelotMiddlewareConfigurationDelegate.cs @@ -0,0 +1,7 @@ +namespace Ocelot.Middleware +{ + using System.Threading.Tasks; + using Microsoft.AspNetCore.Builder; + + public delegate Task OcelotMiddlewareConfigurationDelegate(IApplicationBuilder builder); +} diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index a35a8e1a..14877d4f 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -1,250 +1,162 @@ -namespace Ocelot.Middleware -{ - using System; - using System.Linq; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Hosting; - using Microsoft.Extensions.Options; - using System.Diagnostics; - using Microsoft.AspNetCore.Builder; - using Ocelot.Configuration; - using Ocelot.Configuration.Creator; - using Ocelot.Configuration.File; - using Ocelot.Configuration.Repository; - using Ocelot.Configuration.Setter; - using Ocelot.Responses; - using Ocelot.Logging; - using Rafty.Concensus; - using Rafty.Infrastructure; - using Ocelot.Middleware.Pipeline; - using Pivotal.Discovery.Client; - using Rafty.Concensus.Node; - - public static class OcelotMiddlewareExtensions - { - public static async Task UseOcelot(this IApplicationBuilder builder) - { - await builder.UseOcelot(new OcelotPipelineConfiguration()); - - return builder; - } - - public static async Task UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration) - { - var configuration = await CreateConfiguration(builder); - - CreateAdministrationArea(builder, configuration); - - if(UsingRafty(builder)) - { - SetUpRafty(builder); - } - - if (UsingEurekaServiceDiscoveryProvider(configuration)) - { - builder.UseDiscoveryClient(); - } - - ConfigureDiagnosticListener(builder); - - var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices); - - pipelineBuilder.BuildOcelotPipeline(pipelineConfiguration); - - var firstDelegate = pipelineBuilder.Build(); - - /* - inject first delegate into first piece of asp.net middleware..maybe not like this - then because we are updating the http context in ocelot it comes out correct for - rest of asp.net.. - */ - - builder.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware"; - - builder.Use(async (context, task) => - { - var downstreamContext = new DownstreamContext(context); - await firstDelegate.Invoke(downstreamContext); - }); - - 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 CreateConfiguration(IApplicationBuilder builder) - { - // make configuration from file system? - // earlier user needed to add ocelot files in startup configuration stuff, asp.net will map it to this - var fileConfig = (IOptions)builder.ApplicationServices.GetService(typeof(IOptions)); - - // now create the config - var internalConfigCreator = (IInternalConfigurationCreator)builder.ApplicationServices.GetService(typeof(IInternalConfigurationCreator)); - var internalConfig = await internalConfigCreator.Create(fileConfig.Value); - - // now save it in memory - var internalConfigRepo = (IInternalConfigurationRepository)builder.ApplicationServices.GetService(typeof(IInternalConfigurationRepository)); - internalConfigRepo.AddOrReplace(internalConfig.Data); - - var fileConfigSetter = (IFileConfigurationSetter)builder.ApplicationServices.GetService(typeof(IFileConfigurationSetter)); - - var fileConfigRepo = (IFileConfigurationRepository)builder.ApplicationServices.GetService(typeof(IFileConfigurationRepository)); - - if (UsingConsul(fileConfigRepo)) - { - await SetFileConfigInConsul(builder, fileConfigRepo, fileConfig, internalConfigCreator, internalConfigRepo); - } - else - { - await SetFileConfig(fileConfigSetter, fileConfig); - } - - return GetOcelotConfigAndReturn(internalConfigRepo); - } - - private static async Task SetFileConfigInConsul(IApplicationBuilder builder, - IFileConfigurationRepository fileConfigRepo, IOptions fileConfig, - IInternalConfigurationCreator internalConfigCreator, IInternalConfigurationRepository internalConfigRepo) - { - // get the config from consul. - var fileConfigFromConsul = await fileConfigRepo.Get(); - - if (IsError(fileConfigFromConsul)) - { - ThrowToStopOcelotStarting(fileConfigFromConsul); - } - 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)) - { - ThrowToStopOcelotStarting(response); - } - } - - 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 fileConfig) - { - Response response; - response = await fileConfigSetter.Set(fileConfig.Value); - - if (IsError(response)) - { - ThrowToStopOcelotStarting(response); - } - } - - private static bool ConfigNotStoredInConsul(Responses.Response fileConfigFromConsul) - { - return fileConfigFromConsul.Data == null; - } - - private static bool IsError(Response response) - { - return response == null || response.IsError; - } - - private static IInternalConfiguration GetOcelotConfigAndReturn(IInternalConfigurationRepository provider) - { - var ocelotConfiguration = provider.Get(); - - if(ocelotConfiguration?.Data == null || ocelotConfiguration.IsError) - { - ThrowToStopOcelotStarting(ocelotConfiguration); - } - - return ocelotConfiguration.Data; - } - - private static void ThrowToStopOcelotStarting(Response config) - { - 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) - { - var env = (IHostingEnvironment)builder.ApplicationServices.GetService(typeof(IHostingEnvironment)); - var listener = (OcelotDiagnosticListener)builder.ApplicationServices.GetService(typeof(OcelotDiagnosticListener)); - var diagnosticListener = (DiagnosticListener)builder.ApplicationServices.GetService(typeof(DiagnosticListener)); - diagnosticListener.SubscribeWithAdapter(listener); - } - - private static void OnShutdown(IApplicationBuilder app) - { - var node = (INode)app.ApplicationServices.GetService(typeof(INode)); - node.Stop(); - } - } -} +namespace Ocelot.Middleware +{ + using System; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.Options; + using System.Diagnostics; + using DependencyInjection; + using Microsoft.AspNetCore.Builder; + using Ocelot.Configuration; + using Ocelot.Configuration.Creator; + using Ocelot.Configuration.File; + using Ocelot.Configuration.Repository; + using Ocelot.Configuration.Setter; + using Ocelot.Responses; + using Ocelot.Logging; + using Ocelot.Middleware.Pipeline; + using Microsoft.Extensions.DependencyInjection; + + public static class OcelotMiddlewareExtensions + { + public static async Task UseOcelot(this IApplicationBuilder builder) + { + await builder.UseOcelot(new OcelotPipelineConfiguration()); + return builder; + } + + public static async Task UseOcelot(this IApplicationBuilder builder, Action pipelineConfiguration) + { + var config = new OcelotPipelineConfiguration(); + pipelineConfiguration?.Invoke(config); + return await builder.UseOcelot(config); + } + + public static async Task UseOcelot(this IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration) + { + var configuration = await CreateConfiguration(builder); + + ConfigureDiagnosticListener(builder); + + return CreateOcelotPipeline(builder, pipelineConfiguration); + } + + private static IApplicationBuilder CreateOcelotPipeline(IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration) + { + var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices); + + pipelineBuilder.BuildOcelotPipeline(pipelineConfiguration); + + var firstDelegate = pipelineBuilder.Build(); + + /* + inject first delegate into first piece of asp.net middleware..maybe not like this + then because we are updating the http context in ocelot it comes out correct for + rest of asp.net.. + */ + + builder.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware"; + + builder.Use(async (context, task) => + { + var downstreamContext = new DownstreamContext(context); + await firstDelegate.Invoke(downstreamContext); + }); + + return builder; + } + + private static async Task CreateConfiguration(IApplicationBuilder builder) + { + // make configuration from file system? + // earlier user needed to add ocelot files in startup configuration stuff, asp.net will map it to this + var fileConfig = builder.ApplicationServices.GetService>(); + + // now create the config + var internalConfigCreator = builder.ApplicationServices.GetService(); + var internalConfig = await internalConfigCreator.Create(fileConfig.CurrentValue); + //Configuration error, throw error message + if (internalConfig.IsError) + { + ThrowToStopOcelotStarting(internalConfig); + } + + // now save it in memory + var internalConfigRepo = builder.ApplicationServices.GetService(); + internalConfigRepo.AddOrReplace(internalConfig.Data); + + fileConfig.OnChange(async (config) => + { + var newInternalConfig = await internalConfigCreator.Create(config); + internalConfigRepo.AddOrReplace(newInternalConfig.Data); + }); + + var adminPath = builder.ApplicationServices.GetService(); + + var configurations = builder.ApplicationServices.GetServices(); + + // 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); + } + + 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(); + + await SetFileConfig(fileConfigSetter, fileConfig); + } + + return GetOcelotConfigAndReturn(internalConfigRepo); + } + + private static bool AdministrationApiInUse(IAdministrationPath adminPath) + { + return adminPath != null; + } + + private static async Task SetFileConfig(IFileConfigurationSetter fileConfigSetter, IOptionsMonitor fileConfig) + { + var response = await fileConfigSetter.Set(fileConfig.CurrentValue); + + if (IsError(response)) + { + ThrowToStopOcelotStarting(response); + } + } + + private static bool IsError(Response response) + { + return response == null || response.IsError; + } + + private static IInternalConfiguration GetOcelotConfigAndReturn(IInternalConfigurationRepository provider) + { + var ocelotConfiguration = provider.Get(); + + if (ocelotConfiguration?.Data == null || ocelotConfiguration.IsError) + { + ThrowToStopOcelotStarting(ocelotConfiguration); + } + + return ocelotConfiguration.Data; + } + + private static void ThrowToStopOcelotStarting(Response config) + { + throw new Exception($"Unable to start Ocelot, errors are: {string.Join(",", config.Errors.Select(x => x.ToString()))}"); + } + + private static void ConfigureDiagnosticListener(IApplicationBuilder builder) + { + var env = builder.ApplicationServices.GetService(); + var listener = builder.ApplicationServices.GetService(); + var diagnosticListener = builder.ApplicationServices.GetService(); + diagnosticListener.SubscribeWithAdapter(listener); + } + } +} diff --git a/src/Ocelot/Middleware/OcelotPipelineConfiguration.cs b/src/Ocelot/Middleware/OcelotPipelineConfiguration.cs index d58b8b09..c7f6e231 100644 --- a/src/Ocelot/Middleware/OcelotPipelineConfiguration.cs +++ b/src/Ocelot/Middleware/OcelotPipelineConfiguration.cs @@ -1,6 +1,8 @@ namespace Ocelot.Middleware { + using Ocelot.Middleware.Pipeline; using System; + using System.Collections.Generic; using System.Threading.Tasks; public class OcelotPipelineConfiguration @@ -37,6 +39,10 @@ /// /// This allows the user to implement there own query string manipulation logic /// - public Func, Task> PreQueryStringBuilderMiddleware { get; set; } + public Func, Task> PreQueryStringBuilderMiddleware { get; set; } + /// + /// This is an extension that will branch to different pipes + /// + public List>> MapWhenOcelotPipeline { get; } = new List>>(); } } diff --git a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs index b9aabfe3..bc558533 100644 --- a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs +++ b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs @@ -80,13 +80,14 @@ namespace Ocelot.Middleware.Pipeline var diagnosticListener = (DiagnosticListener)app.ApplicationServices.GetService(typeof(DiagnosticListener)); var middlewareName = ocelotDelegate.Target.GetType().Name; - OcelotRequestDelegate wrapped = context => { + OcelotRequestDelegate wrapped = context => + { try { Write(diagnosticListener, "Ocelot.MiddlewareStarted", middlewareName, context); return ocelotDelegate(context); } - catch(Exception ex) + catch (Exception ex) { WriteException(diagnosticListener, ex, "Ocelot.MiddlewareException", middlewareName, context); throw ex; @@ -117,7 +118,7 @@ namespace Ocelot.Middleware.Pipeline 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 }); } @@ -125,7 +126,7 @@ namespace Ocelot.Middleware.Pipeline 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 }); } @@ -160,6 +161,28 @@ namespace Ocelot.Middleware.Pipeline return app.Use(next => new MapWhenMiddleware(next, options).Invoke); } + public static IOcelotPipelineBuilder MapWhen(this IOcelotPipelineBuilder app, Func 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 Compile(MethodInfo methodinfo, ParameterInfo[] parameters) { var middleware = typeof(T); diff --git a/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs b/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs index f9a74235..1153ccd5 100644 --- a/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs +++ b/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs @@ -48,6 +48,15 @@ namespace Ocelot.Middleware.Pipeline // Then we get the downstream route information 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? builder.UseHttpHeadersTransformationMiddleware(); diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index 4d13aeda..910c4ce5 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 2.0.0 @@ -12,6 +12,7 @@ API Gateway;.NET core https://github.com/TomPallister/Ocelot https://github.com/TomPallister/Ocelot + http://threemammals.com/images/ocelot_logo.png win10-x64;osx.10.11-x64;osx.10.12-x64;win7-x64 false false @@ -25,12 +26,7 @@ True - - - NU1701 - - @@ -49,13 +45,6 @@ all - - - - - - - diff --git a/src/Ocelot/Raft/BearerToken.cs b/src/Ocelot/Raft/BearerToken.cs deleted file mode 100644 index 983dada9..00000000 --- a/src/Ocelot/Raft/BearerToken.cs +++ /dev/null @@ -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; } - } -} diff --git a/src/Ocelot/Raft/ExcludeFromCoverageAttribute.cs b/src/Ocelot/Raft/ExcludeFromCoverageAttribute.cs deleted file mode 100644 index 9ea5544a..00000000 --- a/src/Ocelot/Raft/ExcludeFromCoverageAttribute.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System; - -namespace Ocelot.Raft -{ - [AttributeUsage(AttributeTargets.Class|AttributeTargets.Method|AttributeTargets.Property)] - public class ExcludeFromCoverageAttribute : Attribute{} -} \ No newline at end of file diff --git a/src/Ocelot/Raft/FakeCommand.cs b/src/Ocelot/Raft/FakeCommand.cs deleted file mode 100644 index 45dd1045..00000000 --- a/src/Ocelot/Raft/FakeCommand.cs +++ /dev/null @@ -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; } - } -} diff --git a/src/Ocelot/Raft/FilePeer.cs b/src/Ocelot/Raft/FilePeer.cs deleted file mode 100644 index dc18840d..00000000 --- a/src/Ocelot/Raft/FilePeer.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Ocelot.Raft -{ - [ExcludeFromCoverage] - public class FilePeer - { - public string HostAndPort { get; set; } - } -} diff --git a/src/Ocelot/Raft/FilePeers.cs b/src/Ocelot/Raft/FilePeers.cs deleted file mode 100644 index 7ad32cbe..00000000 --- a/src/Ocelot/Raft/FilePeers.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; - -namespace Ocelot.Raft -{ - [ExcludeFromCoverage] - public class FilePeers - { - public FilePeers() - { - Peers = new List(); - } - - public List Peers {get; set;} - } -} diff --git a/src/Ocelot/Raft/FilePeersProvider.cs b/src/Ocelot/Raft/FilePeersProvider.cs deleted file mode 100644 index 58edae9f..00000000 --- a/src/Ocelot/Raft/FilePeersProvider.cs +++ /dev/null @@ -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 _options; - private readonly List _peers; - private IBaseUrlFinder _finder; - private IInternalConfigurationRepository _repo; - private IIdentityServerConfiguration _identityServerConfig; - - public FilePeersProvider(IOptions options, IBaseUrlFinder finder, IInternalConfigurationRepository repo, IIdentityServerConfiguration identityServerConfig) - { - _identityServerConfig = identityServerConfig; - _repo = repo; - _finder = finder; - _options = options; - _peers = new List(); - - 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 Get() - { - return _peers; - } - } -} diff --git a/src/Ocelot/Raft/HttpPeer.cs b/src/Ocelot/Raft/HttpPeer.cs deleted file mode 100644 index 639f1ee8..00000000 --- a/src/Ocelot/Raft/HttpPeer.cs +++ /dev/null @@ -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 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(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings); - } - - return new RequestVoteResponse(false, requestVote.Term); - } - - public async Task 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(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> Request(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>(await response.Content.ReadAsStringAsync(), _jsonSerializerSettings); - return new OkResponse((T)okResponse.Command); - } - - Console.WriteLine("REQUEST NOT OK...."); - return new ErrorResponse(await response.Content.ReadAsStringAsync(), command); - } - - private async Task SetToken() - { - var tokenUrl = $"{_baseSchemeUrlAndPort}{_config.AdministrationPath}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", _identityServerConfiguration.ApiName), - new KeyValuePair("client_secret", _identityServerConfiguration.ApiSecret), - new KeyValuePair("scope", _identityServerConfiguration.ApiName), - new KeyValuePair("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(responseContent); - _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(_token.TokenType, _token.AccessToken); - } - } -} diff --git a/src/Ocelot/Raft/OcelotFiniteStateMachine.cs b/src/Ocelot/Raft/OcelotFiniteStateMachine.cs deleted file mode 100644 index 618d7f5f..00000000 --- a/src/Ocelot/Raft/OcelotFiniteStateMachine.cs +++ /dev/null @@ -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); - } - } -} diff --git a/src/Ocelot/Raft/RaftController.cs b/src/Ocelot/Raft/RaftController.cs deleted file mode 100644 index 660449c6..00000000 --- a/src/Ocelot/Raft/RaftController.cs +++ /dev/null @@ -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(); - _node = node; - } - - [Route("appendentries")] - public async Task AppendEntries() - { - using(var reader = new StreamReader(HttpContext.Request.Body)) - { - var json = await reader.ReadToEndAsync(); - - var appendEntries = JsonConvert.DeserializeObject(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 RequestVote() - { - using(var reader = new StreamReader(HttpContext.Request.Body)) - { - var json = await reader.ReadToEndAsync(); - - var requestVote = JsonConvert.DeserializeObject(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 Command() - { - try - { - using(var reader = new StreamReader(HttpContext.Request.Body)) - { - var json = await reader.ReadToEndAsync(); - - var command = JsonConvert.DeserializeObject(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; - } - } - } -} diff --git a/src/Ocelot/Raft/SqlLiteLog.cs b/src/Ocelot/Raft/SqlLiteLog.cs deleted file mode 100644 index 99cd0308..00000000 --- a/src/Ocelot/Raft/SqlLiteLog.cs +++ /dev/null @@ -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(); - _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 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 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(data, jsonSerializerSettings); - if (log != null && log.Term > result) - { - result = log.Term; - } - } - } - - _sempaphore.Release(); - return result; - } - - public async Task 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 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(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 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(data, jsonSerializerSettings); - - if (logEntry != null && log != null && logEntry.Term == log.Term) - { - _sempaphore.Release(); - return true; - } - } - } - - _sempaphore.Release(); - return false; - } - - public async Task 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(data, jsonSerializerSettings); - _sempaphore.Release(); - return log; - } - } - } - - public async Task> 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(data, jsonSerializerSettings); - logsToReturn.Add((id, log)); - } - } - } - - _sempaphore.Release(); - return logsToReturn; - } - } - - public async Task 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(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(); - } - } -} diff --git a/src/Ocelot/Raft/UpdateFileConfiguration.cs b/src/Ocelot/Raft/UpdateFileConfiguration.cs deleted file mode 100644 index dfe6a433..00000000 --- a/src/Ocelot/Raft/UpdateFileConfiguration.cs +++ /dev/null @@ -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;} - } -} \ No newline at end of file diff --git a/src/Ocelot/Request/Mapper/IRequestMapper.cs b/src/Ocelot/Request/Mapper/IRequestMapper.cs index 343f1ab2..941a24f7 100644 --- a/src/Ocelot/Request/Mapper/IRequestMapper.cs +++ b/src/Ocelot/Request/Mapper/IRequestMapper.cs @@ -1,12 +1,12 @@ -namespace Ocelot.Request.Mapper -{ - using System.Net.Http; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Http; - using Ocelot.Responses; - - public interface IRequestMapper - { - Task> Map(HttpRequest request); - } -} +namespace Ocelot.Request.Mapper +{ + using System.Net.Http; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Ocelot.Responses; + + public interface IRequestMapper + { + Task> Map(HttpRequest request); + } +} diff --git a/src/Ocelot/Request/Mapper/RequestMapper.cs b/src/Ocelot/Request/Mapper/RequestMapper.cs index 67afc53d..d0c45807 100644 --- a/src/Ocelot/Request/Mapper/RequestMapper.cs +++ b/src/Ocelot/Request/Mapper/RequestMapper.cs @@ -1,108 +1,112 @@ -namespace Ocelot.Request.Mapper -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Net.Http; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Http; - using Microsoft.AspNetCore.Http.Extensions; - using Microsoft.Extensions.Primitives; - using Ocelot.Responses; - - public class RequestMapper : IRequestMapper - { - private readonly string[] _unsupportedHeaders = { "host" }; - - public async Task> Map(HttpRequest request) - { - try - { - var requestMessage = new HttpRequestMessage() - { - Content = await MapContent(request), - Method = MapMethod(request), - RequestUri = MapUri(request) - }; - - MapHeaders(request, requestMessage); - - return new OkResponse(requestMessage); - } - catch (Exception ex) - { - return new ErrorResponse(new UnmappableRequestError(ex)); - } - } - - private async Task MapContent(HttpRequest request) - { - if (request.Body == null) - { - return null; - } - - var content = new ByteArrayContent(await ToByteArray(request.Body)); - - content.Headers - .TryAddWithoutValidation("Content-Type", new[] {request.ContentType}); - - AddHeaderIfExistsOnRequest("Content-Language", content, request); - AddHeaderIfExistsOnRequest("Content-Location", content, request); - AddHeaderIfExistsOnRequest("Content-Range", content, request); - AddHeaderIfExistsOnRequest("Content-MD5", content, request); - AddHeaderIfExistsOnRequest("Content-Disposition", content, request); - AddHeaderIfExistsOnRequest("Content-Encoding", content, request); - - return content; - } - - private void AddHeaderIfExistsOnRequest(string key, HttpContent content, HttpRequest request) - { - if(request.Headers.ContainsKey(key)) - { - content.Headers - .TryAddWithoutValidation(key, request.Headers[key].ToList()); - } - } - - private HttpMethod MapMethod(HttpRequest request) - { - return new HttpMethod(request.Method); - } - - private Uri MapUri(HttpRequest request) - { - return new Uri(request.GetEncodedUrl()); - } - - private void MapHeaders(HttpRequest request, HttpRequestMessage requestMessage) - { - foreach (var header in request.Headers) - { - if (IsSupportedHeader(header)) - { - requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); - } - } - } - - private async Task ToByteArray(Stream stream) - { - using (stream) - { - using (var memStream = new MemoryStream()) - { - await stream.CopyToAsync(memStream); - return memStream.ToArray(); - } - } - } - - private bool IsSupportedHeader(KeyValuePair header) - { - return !_unsupportedHeaders.Contains(header.Key.ToLower()); - } - } -} +namespace Ocelot.Request.Mapper +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Http.Extensions; + using Microsoft.Extensions.Primitives; + using Ocelot.Responses; + + public class RequestMapper : IRequestMapper + { + private readonly string[] _unsupportedHeaders = { "host" }; + + public async Task> Map(HttpRequest request) + { + try + { + var requestMessage = new HttpRequestMessage() + { + Content = await MapContent(request), + Method = MapMethod(request), + RequestUri = MapUri(request) + }; + + MapHeaders(request, requestMessage); + + return new OkResponse(requestMessage); + } + catch (Exception ex) + { + return new ErrorResponse(new UnmappableRequestError(ex)); + } + } + + private async Task MapContent(HttpRequest request) + { + if (request.Body == null || (request.Body.CanSeek && request.Body.Length <= 0)) + { + return null; + } + + // Never change this to StreamContent again, I forgot it doesnt work in #464. + var content = new ByteArrayContent(await ToByteArray(request.Body)); + + if(!string.IsNullOrEmpty(request.ContentType)) + { + content.Headers + .TryAddWithoutValidation("Content-Type", new[] {request.ContentType}); + } + + AddHeaderIfExistsOnRequest("Content-Language", content, request); + AddHeaderIfExistsOnRequest("Content-Location", content, request); + AddHeaderIfExistsOnRequest("Content-Range", content, request); + AddHeaderIfExistsOnRequest("Content-MD5", content, request); + AddHeaderIfExistsOnRequest("Content-Disposition", content, request); + AddHeaderIfExistsOnRequest("Content-Encoding", content, request); + + return content; + } + + private void AddHeaderIfExistsOnRequest(string key, HttpContent content, HttpRequest request) + { + if(request.Headers.ContainsKey(key)) + { + content.Headers + .TryAddWithoutValidation(key, request.Headers[key].ToList()); + } + } + + private HttpMethod MapMethod(HttpRequest request) + { + return new HttpMethod(request.Method); + } + + private Uri MapUri(HttpRequest request) + { + return new Uri(request.GetEncodedUrl()); + } + + private void MapHeaders(HttpRequest request, HttpRequestMessage requestMessage) + { + foreach (var header in request.Headers) + { + if (IsSupportedHeader(header)) + { + requestMessage.Headers.TryAddWithoutValidation(header.Key, header.Value.ToArray()); + } + } + } + + private bool IsSupportedHeader(KeyValuePair header) + { + return !_unsupportedHeaders.Contains(header.Key.ToLower()); + } + + private async Task ToByteArray(Stream stream) + { + using(stream) + { + using (var memStream = new MemoryStream()) + { + await stream.CopyToAsync(memStream); + return memStream.ToArray(); + } + } + } + } +} diff --git a/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs b/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs index 340afb47..a4263ef1 100644 --- a/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs +++ b/src/Ocelot/Requester/DelegatingHandlerHandlerFactory.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; -using Butterfly.Client.Tracing; using Microsoft.Extensions.DependencyInjection; using Ocelot.Configuration; using Ocelot.Logging; diff --git a/src/Ocelot/Requester/FakeServiceTracer.cs b/src/Ocelot/Requester/FakeServiceTracer.cs deleted file mode 100644 index 95347ef6..00000000 --- a/src/Ocelot/Requester/FakeServiceTracer.cs +++ /dev/null @@ -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; - } - } -} diff --git a/src/Ocelot/Requester/OcelotHttpTracingHandler.cs b/src/Ocelot/Requester/OcelotHttpTracingHandler.cs index fd6dce2b..b5585d57 100644 --- a/src/Ocelot/Requester/OcelotHttpTracingHandler.cs +++ b/src/Ocelot/Requester/OcelotHttpTracingHandler.cs @@ -1,21 +1,19 @@ -using System; -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 +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 { - private readonly IServiceTracer _tracer; + private readonly ITracer _tracer; private readonly IRequestScopedDataRepository _repo; - private const string PrefixSpanId = "ot-spanId"; public OcelotHttpTracingHandler( - IServiceTracer tracer, + ITracer tracer, IRequestScopedDataRepository repo, HttpMessageHandler httpMessageHandler = null) { @@ -28,46 +26,8 @@ namespace Ocelot.Requester HttpRequestMessage request, CancellationToken cancellationToken) { - return _tracer.ChildTraceAsync($"httpclient {request.Method}", DateTimeOffset.UtcNow, span => TracingSendAsync(span, request, cancellationToken)); - } - - protected virtual async Task TracingSendAsync( - 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; + return _tracer.SendAsync(request, cancellationToken, x => _repo.Add("TraceId", x), (r,c) => base.SendAsync(r, c)); } } } diff --git a/src/Ocelot/Requester/TracingHandlerFactory.cs b/src/Ocelot/Requester/TracingHandlerFactory.cs index f0eb97b1..2740ef96 100644 --- a/src/Ocelot/Requester/TracingHandlerFactory.cs +++ b/src/Ocelot/Requester/TracingHandlerFactory.cs @@ -1,19 +1,21 @@ -using Butterfly.Client.Tracing; -using Ocelot.Infrastructure.RequestData; - namespace Ocelot.Requester { + using System; + using Logging; + using Ocelot.Infrastructure.RequestData; + using Microsoft.Extensions.DependencyInjection; + public class TracingHandlerFactory : ITracingHandlerFactory { - private readonly IServiceTracer _tracer; + private readonly ITracer _tracer; private readonly IRequestScopedDataRepository _repo; public TracingHandlerFactory( - IServiceTracer tracer, + IServiceProvider services, IRequestScopedDataRepository repo) { _repo = repo; - _tracer = tracer; + _tracer = services.GetService(); } public ITracingHandler Get() diff --git a/src/Ocelot/Responder/HttpContextResponder.cs b/src/Ocelot/Responder/HttpContextResponder.cs index 0b2959b4..6511590c 100644 --- a/src/Ocelot/Responder/HttpContextResponder.cs +++ b/src/Ocelot/Responder/HttpContextResponder.cs @@ -1,74 +1,74 @@ -using System.IO; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Primitives; -using Ocelot.Headers; -using Ocelot.Middleware; - -namespace Ocelot.Responder -{ - /// - /// Cannot unit test things in this class due to methods not being implemented - /// on .net concretes used for testing - /// - public class HttpContextResponder : IHttpResponder - { - private readonly IRemoveOutputHeaders _removeOutputHeaders; - - public HttpContextResponder(IRemoveOutputHeaders removeOutputHeaders) - { - _removeOutputHeaders = removeOutputHeaders; - } - - public async Task SetResponseOnHttpContext(HttpContext context, DownstreamResponse response) - { - _removeOutputHeaders.Remove(response.Headers); - - foreach (var httpResponseHeader in response.Headers) - { - AddHeaderIfDoesntExist(context, httpResponseHeader); - } - - foreach (var httpResponseHeader in response.Content.Headers) - { - AddHeaderIfDoesntExist(context, new Header(httpResponseHeader.Key, httpResponseHeader.Value)); - } - - var content = await response.Content.ReadAsByteArrayAsync(); - - AddHeaderIfDoesntExist(context, new Header("Content-Length", new []{ content.Length.ToString() }) ); - - context.Response.OnStarting(state => - { - var httpContext = (HttpContext)state; - - httpContext.Response.StatusCode = (int)response.StatusCode; - - return Task.CompletedTask; - }, context); - - using (Stream stream = new MemoryStream(content)) - { - if (response.StatusCode != HttpStatusCode.NotModified && context.Response.ContentLength != 0) - { - await stream.CopyToAsync(context.Response.Body); - } - } - } - - public void SetErrorResponseOnContext(HttpContext context, int statusCode) +using System.IO; +using System.Linq; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; +using Ocelot.Headers; +using Ocelot.Middleware; + +namespace Ocelot.Responder +{ + /// + /// Cannot unit test things in this class due to methods not being implemented + /// on .net concretes used for testing + /// + public class HttpContextResponder : IHttpResponder + { + private readonly IRemoveOutputHeaders _removeOutputHeaders; + + public HttpContextResponder(IRemoveOutputHeaders removeOutputHeaders) { - context.Response.StatusCode = statusCode; - } - - private static void AddHeaderIfDoesntExist(HttpContext context, Header httpResponseHeader) - { - if (!context.Response.Headers.ContainsKey(httpResponseHeader.Key)) - { - context.Response.Headers.Add(httpResponseHeader.Key, new StringValues(httpResponseHeader.Values.ToArray())); - } - } - } -} + _removeOutputHeaders = removeOutputHeaders; + } + + public async Task SetResponseOnHttpContext(HttpContext context, DownstreamResponse response) + { + _removeOutputHeaders.Remove(response.Headers); + + foreach (var httpResponseHeader in response.Headers) + { + AddHeaderIfDoesntExist(context, httpResponseHeader); + } + + foreach (var httpResponseHeader in response.Content.Headers) + { + AddHeaderIfDoesntExist(context, new Header(httpResponseHeader.Key, httpResponseHeader.Value)); + } + + var content = await response.Content.ReadAsStreamAsync(); + + AddHeaderIfDoesntExist(context, new Header("Content-Length", new []{ content.Length.ToString() }) ); + + context.Response.OnStarting(state => + { + var httpContext = (HttpContext)state; + + httpContext.Response.StatusCode = (int)response.StatusCode; + + return Task.CompletedTask; + }, context); + + using(content) + { + if (response.StatusCode != HttpStatusCode.NotModified && context.Response.ContentLength != 0) + { + await content.CopyToAsync(context.Response.Body); + } + } + } + + public void SetErrorResponseOnContext(HttpContext context, int statusCode) + { + context.Response.StatusCode = statusCode; + } + + private static void AddHeaderIfDoesntExist(HttpContext context, Header httpResponseHeader) + { + if (!context.Response.Headers.ContainsKey(httpResponseHeader.Key)) + { + context.Response.Headers.Add(httpResponseHeader.Key, new StringValues(httpResponseHeader.Values.ToArray())); + } + } + } +} diff --git a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs index 0487d811..ec6696c5 100644 --- a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs +++ b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs @@ -1,55 +1,55 @@ -using Microsoft.AspNetCore.Http; -using Ocelot.Errors; -using Ocelot.Logging; -using Ocelot.Middleware; -using System.Collections.Generic; -using System.Threading.Tasks; -using Ocelot.Infrastructure.Extensions; - -namespace Ocelot.Responder.Middleware -{ - /// - /// Completes and returns the request and request body, if any pipeline errors occured then sets the appropriate HTTP status code instead. - /// - public class ResponderMiddleware : OcelotMiddleware - { - private readonly OcelotRequestDelegate _next; - private readonly IHttpResponder _responder; - private readonly IErrorsToHttpStatusCodeMapper _codeMapper; - - public ResponderMiddleware(OcelotRequestDelegate next, - IHttpResponder responder, - IOcelotLoggerFactory loggerFactory, - IErrorsToHttpStatusCodeMapper codeMapper - ) - :base(loggerFactory.CreateLogger()) - { - _next = next; - _responder = responder; - _codeMapper = codeMapper; - } - - public async Task Invoke(DownstreamContext context) - { - await _next.Invoke(context); - - if (context.IsError) - { - Logger.LogWarning($"{context.Errors.ToErrorString()} errors found in {MiddlewareName}. Setting error response for request path:{context.HttpContext.Request.Path}, request method: {context.HttpContext.Request.Method}"); - - SetErrorResponse(context.HttpContext, context.Errors); - } - else - { - Logger.LogDebug("no pipeline errors, setting and returning completed response"); - await _responder.SetResponseOnHttpContext(context.HttpContext, context.DownstreamResponse); - } - } - - private void SetErrorResponse(HttpContext context, List errors) - { - var statusCode = _codeMapper.Map(errors); - _responder.SetErrorResponseOnContext(context, statusCode); - } - } -} +using Microsoft.AspNetCore.Http; +using Ocelot.Errors; +using Ocelot.Logging; +using Ocelot.Middleware; +using System.Collections.Generic; +using System.Threading.Tasks; +using Ocelot.Infrastructure.Extensions; + +namespace Ocelot.Responder.Middleware +{ + /// + /// Completes and returns the request and request body, if any pipeline errors occured then sets the appropriate HTTP status code instead. + /// + public class ResponderMiddleware : OcelotMiddleware + { + private readonly OcelotRequestDelegate _next; + private readonly IHttpResponder _responder; + private readonly IErrorsToHttpStatusCodeMapper _codeMapper; + + public ResponderMiddleware(OcelotRequestDelegate next, + IHttpResponder responder, + IOcelotLoggerFactory loggerFactory, + IErrorsToHttpStatusCodeMapper codeMapper + ) + :base(loggerFactory.CreateLogger()) + { + _next = next; + _responder = responder; + _codeMapper = codeMapper; + } + + public async Task Invoke(DownstreamContext context) + { + await _next.Invoke(context); + + if (context.IsError) + { + Logger.LogWarning($"{context.Errors.ToErrorString()} errors found in {MiddlewareName}. Setting error response for request path:{context.HttpContext.Request.Path}, request method: {context.HttpContext.Request.Method}"); + + SetErrorResponse(context.HttpContext, context.Errors); + } + else + { + Logger.LogDebug("no pipeline errors, setting and returning completed response"); + await _responder.SetResponseOnHttpContext(context.HttpContext, context.DownstreamResponse); + } + } + + private void SetErrorResponse(HttpContext context, List errors) + { + var statusCode = _codeMapper.Map(errors); + _responder.SetErrorResponseOnContext(context, statusCode); + } + } +} diff --git a/src/Ocelot/ServiceDiscovery/Configuration/ConsulRegistryConfiguration.cs b/src/Ocelot/ServiceDiscovery/Configuration/ConsulRegistryConfiguration.cs deleted file mode 100644 index b99f43cc..00000000 --- a/src/Ocelot/ServiceDiscovery/Configuration/ConsulRegistryConfiguration.cs +++ /dev/null @@ -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; } - } -} diff --git a/src/Ocelot/ServiceDiscovery/Errors/UnableToFindServiceDiscoveryProviderError.cs b/src/Ocelot/ServiceDiscovery/Errors/UnableToFindServiceDiscoveryProviderError.cs deleted file mode 100644 index a31ed2ee..00000000 --- a/src/Ocelot/ServiceDiscovery/Errors/UnableToFindServiceDiscoveryProviderError.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Ocelot.Errors; - -namespace Ocelot.ServiceDiscovery.Errors -{ - public class UnableToFindServiceDiscoveryProviderError : Error - { - public UnableToFindServiceDiscoveryProviderError(string message) - : base(message, OcelotErrorCode.UnableToFindServiceDiscoveryProviderError) - { - } - } -} diff --git a/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs deleted file mode 100644 index 88bcc5d5..00000000 --- a/src/Ocelot/ServiceDiscovery/Providers/ConsulServiceDiscoveryProvider.cs +++ /dev/null @@ -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(); - - _config = config; - _consul = clientFactory.Get(_config); - } - - public async Task> Get() - { - var queryResult = await _consul.Health.Service(_config.KeyOfServiceInConsul, string.Empty, true); - - var services = new List(); - - 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()); - } - - 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 strings) - { - return strings - ?.FirstOrDefault(x => x.StartsWith(VersionPrefix, StringComparison.Ordinal)) - .TrimStart(VersionPrefix); - } - } -} diff --git a/src/Ocelot/ServiceDiscovery/Providers/EurekaServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/Providers/EurekaServiceDiscoveryProvider.cs deleted file mode 100644 index a9f12701..00000000 --- a/src/Ocelot/ServiceDiscovery/Providers/EurekaServiceDiscoveryProvider.cs +++ /dev/null @@ -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> Get() - { - var services = new List(); - - 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()))); - } - - return Task.FromResult(services); - } - } -} diff --git a/src/Ocelot/ServiceDiscovery/Providers/FakeEurekaDiscoveryClient.cs b/src/Ocelot/ServiceDiscovery/Providers/FakeEurekaDiscoveryClient.cs deleted file mode 100644 index 5ccc382d..00000000 --- a/src/Ocelot/ServiceDiscovery/Providers/FakeEurekaDiscoveryClient.cs +++ /dev/null @@ -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 GetInstances(string serviceId) - { - throw new System.NotImplementedException(); - } - - public Task ShutdownAsync() - { - throw new System.NotImplementedException(); - } - - public string Description { get; } - public IList Services { get; } - } -} diff --git a/src/Ocelot/ServiceDiscovery/Providers/PolingConsulServiceDiscoveryProvider.cs b/src/Ocelot/ServiceDiscovery/Providers/PolingConsulServiceDiscoveryProvider.cs deleted file mode 100644 index 4206f7c1..00000000 --- a/src/Ocelot/ServiceDiscovery/Providers/PolingConsulServiceDiscoveryProvider.cs +++ /dev/null @@ -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 _services; - private string _keyOfServiceInConsul; - - public PollingConsulServiceDiscoveryProvider(int pollingInterval, string keyOfServiceInConsul, IOcelotLoggerFactory factory, IServiceDiscoveryProvider consulServiceDiscoveryProvider) - {; - _logger = factory.CreateLogger(); - _keyOfServiceInConsul = keyOfServiceInConsul; - _consulServiceDiscoveryProvider = consulServiceDiscoveryProvider; - _services = new List(); - - _timer = new Timer(async x => - { - if(_polling) - { - return; - } - - _polling = true; - await Poll(); - _polling = false; - - }, null, pollingInterval, pollingInterval); - } - - public Task> Get() - { - return Task.FromResult(_services); - } - - private async Task Poll() - { - _services = await _consulServiceDiscoveryProvider.Get(); - } - } -} diff --git a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryFinderDelegate.cs b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryFinderDelegate.cs new file mode 100644 index 00000000..e3b0f20c --- /dev/null +++ b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryFinderDelegate.cs @@ -0,0 +1,8 @@ +namespace Ocelot.ServiceDiscovery +{ + using System; + using Ocelot.Configuration; + using Providers; + + public delegate IServiceDiscoveryProvider ServiceDiscoveryFinderDelegate(IServiceProvider provider, ServiceProviderConfiguration config, string key); +} diff --git a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs index 88b4ce79..503814e9 100644 --- a/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs +++ b/src/Ocelot/ServiceDiscovery/ServiceDiscoveryProviderFactory.cs @@ -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 -{ - 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 { private readonly IOcelotLoggerFactory _factory; - private readonly IConsulClientFactory _consulFactory; - private readonly IDiscoveryClient _eurekaClient; + private readonly List _delegates; + private readonly IServiceProvider _provider; - public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory, IConsulClientFactory consulFactory, IDiscoveryClient eurekaClient) + public ServiceDiscoveryProviderFactory(IOcelotLoggerFactory factory, IServiceProvider provider) { _factory = factory; - _consulFactory = consulFactory; - _eurekaClient = eurekaClient; + _provider = provider; + _delegates = provider + .GetServices() + .ToList(); } public IServiceDiscoveryProvider Get(ServiceProviderConfiguration serviceConfig, DownstreamReRoute reRoute) @@ -42,29 +44,24 @@ namespace Ocelot.ServiceDiscovery 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); - return new ServiceFabricServiceDiscoveryProvider(config); + var sfConfig = new ServiceFabricConfiguration(config.Host, config.Port, key); + return new ServiceFabricServiceDiscoveryProvider(sfConfig); } - if (serviceConfig.Type?.ToLower() == "eureka") + foreach (var serviceDiscoveryFinderDelegate in _delegates) { - return new EurekaServiceDiscoveryProvider(serviceName, _eurekaClient); - } - - var consulRegistryConfiguration = new ConsulRegistryConfiguration(serviceConfig.Host, serviceConfig.Port, serviceName, serviceConfig.Token); - - var consulServiceDiscoveryProvider = new ConsulServiceDiscoveryProvider(consulRegistryConfiguration, _factory, _consulFactory); - - if (serviceConfig.Type?.ToLower() == "pollconsul") - { - return new PollingConsulServiceDiscoveryProvider(serviceConfig.PollingInterval, consulRegistryConfiguration.KeyOfServiceInConsul, _factory, consulServiceDiscoveryProvider); + var provider = serviceDiscoveryFinderDelegate?.Invoke(_provider, config, key); + if (provider != null) + { + return provider; + } } - return consulServiceDiscoveryProvider; + return null; } } } diff --git a/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs b/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs index 68b2df48..033653ca 100644 --- a/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs +++ b/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs @@ -14,7 +14,7 @@ namespace Ocelot.WebSockets.Middleware public WebSocketsProxyMiddleware(OcelotRequestDelegate next, IOcelotLoggerFactory loggerFactory) - :base(loggerFactory.CreateLogger()) + : base(loggerFactory.CreateLogger()) { _next = next; } @@ -29,6 +29,17 @@ namespace Ocelot.WebSockets.Middleware var wsToUpstreamClient = await context.WebSockets.AcceptWebSocketAsync(); 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); await wsToDownstreamService.ConnectAsync(uri, CancellationToken.None); diff --git a/test/Ocelot.AcceptanceTests/AggregateTests.cs b/test/Ocelot.AcceptanceTests/AggregateTests.cs index 8e0e5dfd..f28d6bc7 100644 --- a/test/Ocelot.AcceptanceTests/AggregateTests.cs +++ b/test/Ocelot.AcceptanceTests/AggregateTests.cs @@ -20,14 +20,14 @@ namespace Ocelot.AcceptanceTests { public class AggregateTests : IDisposable { - private IWebHost _serviceOneBuilder; - private IWebHost _serviceTwoBuilder; private readonly Steps _steps; private string _downstreamPathOne; private string _downstreamPathTwo; + private readonly ServiceHandler _serviceHandler; public AggregateTests() { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); } @@ -116,7 +116,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51885, + Port = 51875, } }, UpstreamPathTemplate = "/laura", @@ -157,7 +157,7 @@ namespace Ocelot.AcceptanceTests 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}")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) @@ -370,64 +370,40 @@ namespace Ocelot.AcceptanceTests private void GivenServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) { - _serviceOneBuilder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => + { + _downstreamPathOne = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; + + if (_downstreamPathOne != basePath) { - 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(); + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + }); } private void GivenServiceTwoIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) { - _serviceTwoBuilder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => + { + _downstreamPathTwo = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; + + if (_downstreamPathTwo != basePath) { - 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(); + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + }); } internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPathOne, string expectedDownstreamPath) @@ -438,8 +414,7 @@ namespace Ocelot.AcceptanceTests public void Dispose() { - _serviceOneBuilder?.Dispose(); - _serviceTwoBuilder?.Dispose(); + _serviceHandler.Dispose(); _steps.Dispose(); } } diff --git a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs index 770deaa1..a5ba79dd 100644 --- a/test/Ocelot.AcceptanceTests/AuthenticationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthenticationTests.cs @@ -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 { 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 { - private IWebHost _servicebuilder; private readonly Steps _steps; private IWebHost _identityServerBuilder; private string _identityServerRootUrl = "http://localhost:51888"; private string _downstreamServicePath = "/"; private string _downstreamServiceHost = "localhost"; - private int _downstreamServicePort = 51876; private string _downstreamServiceScheme = "http"; - private string _downstreamServiceUrl = "http://localhost:51876"; + private string _downstreamServiceUrl = "http://localhost:"; private readonly Action _options; + private readonly ServiceHandler _serviceHandler; public AuthenticationTests() { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); _options = o => { @@ -46,6 +45,8 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_return_401_using_identity_server_access_token() { + int port = 54329; + var configuration = new FileConfiguration { ReRoutes = new List @@ -58,7 +59,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host =_downstreamServiceHost, - Port = _downstreamServicePort, + Port = port, } }, DownstreamScheme = _downstreamServiceScheme, @@ -73,7 +74,7 @@ namespace Ocelot.AcceptanceTests }; 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.GivenOcelotIsRunning(_options, "Test")) .And(x => _steps.GivenThePostHasContent("postContent")) @@ -85,7 +86,9 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_return_response_200_using_identity_server() { - var configuration = new FileConfiguration + int port = 54099; + + var configuration = new FileConfiguration { ReRoutes = new List { @@ -97,7 +100,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host =_downstreamServiceHost, - Port = _downstreamServicePort, + Port = port, } }, DownstreamScheme = _downstreamServiceScheme, @@ -112,7 +115,7 @@ namespace Ocelot.AcceptanceTests }; 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.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) @@ -126,7 +129,9 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_return_response_401_using_identity_server_with_token_requested_for_other_api() { - var configuration = new FileConfiguration + int port = 54196; + + var configuration = new FileConfiguration { ReRoutes = new List { @@ -138,7 +143,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host =_downstreamServiceHost, - Port = _downstreamServicePort, + Port = port, } }, DownstreamScheme = _downstreamServiceScheme, @@ -153,7 +158,7 @@ namespace Ocelot.AcceptanceTests }; 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.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) @@ -166,7 +171,9 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_return_201_using_identity_server_access_token() { - var configuration = new FileConfiguration + int port = 52226; + + var configuration = new FileConfiguration { ReRoutes = new List { @@ -178,7 +185,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host =_downstreamServiceHost, - Port = _downstreamServicePort, + Port = port, } }, DownstreamScheme = _downstreamServiceScheme, @@ -193,7 +200,7 @@ namespace Ocelot.AcceptanceTests }; 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.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) @@ -207,7 +214,9 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_return_201_using_identity_server_reference_token() { - var configuration = new FileConfiguration + int port = 52222; + + var configuration = new FileConfiguration { ReRoutes = new List { @@ -219,7 +228,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host =_downstreamServiceHost, - Port = _downstreamServicePort, + Port = port, } }, DownstreamScheme = _downstreamServiceScheme, @@ -234,7 +243,7 @@ namespace Ocelot.AcceptanceTests }; 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.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) @@ -247,23 +256,11 @@ namespace Ocelot.AcceptanceTests private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) { - _servicebuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) - .Build(); - - _servicebuilder.Start(); + _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); } private void GivenThereIsAnIdentityServerOn(string url, string apiName, string api2Name, AccessTokenType tokenType) @@ -371,7 +368,7 @@ namespace Ocelot.AcceptanceTests public void Dispose() { - _servicebuilder?.Dispose(); + _serviceHandler.Dispose(); _steps.Dispose(); _identityServerBuilder?.Dispose(); } diff --git a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs index 06fee4ef..5dc59b9e 100644 --- a/test/Ocelot.AcceptanceTests/AuthorisationTests.cs +++ b/test/Ocelot.AcceptanceTests/AuthorisationTests.cs @@ -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 { - 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; public class AuthorisationTests : IDisposable { - private IWebHost _servicebuilder; private IWebHost _identityServerBuilder; private readonly Steps _steps; - private Action _options; + private readonly Action _options; private string _identityServerRootUrl = "http://localhost:51888"; + private readonly ServiceHandler _serviceHandler; public AuthorisationTests() { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); _options = o => { @@ -42,8 +41,10 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_return_response_200_authorising_route() { - var configuration = new FileConfiguration - { + int port = 52875; + + var configuration = new FileConfiguration + { ReRoutes = new List { new FileReRoute @@ -54,7 +55,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51876, + Port = port, } }, DownstreamScheme = "http", @@ -83,10 +84,10 @@ namespace Ocelot.AcceptanceTests } } } - }; + }; 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.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) @@ -100,7 +101,9 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_return_response_403_authorising_route() { - var configuration = new FileConfiguration + int port = 59471; + + var configuration = new FileConfiguration { ReRoutes = new List { @@ -112,7 +115,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51876, + Port = port, } }, DownstreamScheme = "http", @@ -143,7 +146,7 @@ namespace Ocelot.AcceptanceTests }; 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.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) @@ -156,7 +159,9 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_return_response_200_using_identity_server_with_allowed_scope() { - var configuration = new FileConfiguration + int port = 63471; + + var configuration = new FileConfiguration { ReRoutes = new List { @@ -168,7 +173,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51876, + Port = port, } }, DownstreamScheme = "http", @@ -184,7 +189,7 @@ namespace Ocelot.AcceptanceTests }; 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.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) @@ -197,7 +202,9 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_return_response_403_using_identity_server_with_scope_not_allowed() { - var configuration = new FileConfiguration + int port = 60571; + + var configuration = new FileConfiguration { ReRoutes = new List { @@ -209,7 +216,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51876, + Port = port, } }, DownstreamScheme = "http", @@ -225,7 +232,7 @@ namespace Ocelot.AcceptanceTests }; 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.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) @@ -238,7 +245,9 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_fix_issue_240() { - var configuration = new FileConfiguration + int port = 61071; + + var configuration = new FileConfiguration { ReRoutes = new List { @@ -250,7 +259,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51876, + Port = port, } }, DownstreamScheme = "http", @@ -284,7 +293,7 @@ namespace Ocelot.AcceptanceTests }; 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.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning(_options, "Test")) @@ -297,23 +306,11 @@ namespace Ocelot.AcceptanceTests private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody) { - _servicebuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) - .Build(); - - _servicebuilder.Start(); + _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); } private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType) @@ -465,7 +462,7 @@ namespace Ocelot.AcceptanceTests public void Dispose() { - _servicebuilder?.Dispose(); + _serviceHandler?.Dispose(); _steps.Dispose(); _identityServerBuilder?.Dispose(); } diff --git a/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs b/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs deleted file mode 100644 index 51f459ef..00000000 --- a/test/Ocelot.AcceptanceTests/ButterflyTracingTests.cs +++ /dev/null @@ -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 - { - new FileReRoute - { - DownstreamPathTemplate = "/api/values", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51887, - } - }, - UpstreamPathTemplate = "/api001/values", - UpstreamHttpMethod = new List { "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 - { - new FileHostAndPort - { - Host = "localhost", - Port = 51388, - } - }, - UpstreamPathTemplate = "/api002/values", - UpstreamHttpMethod = new List { "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 - { - new FileReRoute - { - DownstreamPathTemplate = "/api/values", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51387, - } - }, - UpstreamPathTemplate = "/api001/values", - UpstreamHttpMethod = new List { "Get" }, - HttpHandlerOptions = new FileHttpHandlerOptions - { - UseTracing = true - }, - QoSOptions = new FileQoSOptions - { - ExceptionsAllowedBeforeBreaking = 3, - DurationOfBreak = 10, - TimeoutValue = 5000 - }, - DownstreamHeaderTransform = new Dictionary() - { - {"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(); - } - } -} diff --git a/test/Ocelot.AcceptanceTests/Caching/InMemoryJsonHandle.cs b/test/Ocelot.AcceptanceTests/Caching/InMemoryJsonHandle.cs deleted file mode 100644 index 3f160a7e..00000000 --- a/test/Ocelot.AcceptanceTests/Caching/InMemoryJsonHandle.cs +++ /dev/null @@ -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 : BaseCacheHandle - { - private readonly ICacheSerializer _serializer; - private readonly ConcurrentDictionary> _cache; - - public InMemoryJsonHandle( - ICacheManagerConfiguration managerConfiguration, - CacheHandleConfiguration configuration, - ICacheSerializer serializer, - ILoggerFactory loggerFactory) : base(managerConfiguration, configuration) - { - _cache = new ConcurrentDictionary>(); - _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 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 item) - { - NotNull(item, nameof(item)); - - var key = GetKey(item.Key, item.Region); - - var serializedItem = _serializer.SerializeCacheItem(item); - - return _cache.TryAdd(key, new Tuple(item.Value.GetType(), serializedItem)); - } - - protected override CacheItem GetCacheItemInternal(string key) => GetCacheItemInternal(key, null); - - protected override CacheItem GetCacheItemInternal(string key, string region) - { - var fullKey = GetKey(key, region); - - CacheItem deserializedResult = null; - - if (_cache.TryGetValue(fullKey, out Tuple result)) - { - deserializedResult = _serializer.DeserializeCacheItem(result.Item2, result.Item1); - - if (deserializedResult.ExpirationMode != ExpirationMode.None && IsExpired(deserializedResult, DateTime.UtcNow)) - { - _cache.TryRemove(fullKey, out Tuple removeResult); - TriggerCacheSpecificRemove(key, region, CacheItemRemovedReason.Expired, deserializedResult.Value); - return null; - } - } - - return deserializedResult; - } - - protected override void PutInternalPrepared(CacheItem item) - { - NotNull(item, nameof(item)); - - var serializedItem = _serializer.SerializeCacheItem(item); - - _cache[GetKey(item.Key, item.Region)] = new Tuple(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 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 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; - } - } -} diff --git a/test/Ocelot.AcceptanceTests/CachingTests.cs b/test/Ocelot.AcceptanceTests/CachingTests.cs deleted file mode 100644 index ad2a12b8..00000000 --- a/test/Ocelot.AcceptanceTests/CachingTests.cs +++ /dev/null @@ -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 - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51899, - } - }, - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "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 - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 52839, - } - }, - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "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 - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51899, - } - }, - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "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 - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51899, - } - }, - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "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(); - } - } -} diff --git a/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs b/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs index 7176ce26..ada4355c 100644 --- a/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/CaseSensitiveRoutingTests.cs @@ -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 { + 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 { - private IWebHost _builder; private readonly Steps _steps; + private readonly ServiceHandler _serviceHandler; public CaseSensitiveRoutingTests() { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); } @@ -226,29 +224,16 @@ namespace Ocelot.AcceptanceTests private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) { - _builder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(baseUrl) - .Configure(app => - { - app.UsePathBase(basePath); - app.Run(async context => - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) - .Build(); - - _builder.Start(); + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); } public void Dispose() { - _builder?.Dispose(); + _serviceHandler?.Dispose(); _steps.Dispose(); } } diff --git a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs index 54182334..683b7196 100644 --- a/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs +++ b/test/Ocelot.AcceptanceTests/ClaimsToHeadersForwardingTests.cs @@ -1,35 +1,35 @@ -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; -using Xunit; +using Xunit; [assembly: CollectionBehavior(DisableTestParallelization = true)] namespace Ocelot.AcceptanceTests { - using IdentityServer4; 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 { - private IWebHost _servicebuilder; private IWebHost _identityServerBuilder; private readonly Steps _steps; private Action _options; private string _identityServerRootUrl = "http://localhost:52888"; + private readonly ServiceHandler _serviceHandler; public ClaimsToHeadersForwardingTests() { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); _options = o => { @@ -107,29 +107,17 @@ namespace Ocelot.AcceptanceTests private void GivenThereIsAServiceRunningOn(string url, int statusCode) { - _servicebuilder = new WebHostBuilder() - .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 locationId = context.Request.Headers.First(x => x.Key == "LocationId").Value.First(); - var userType = context.Request.Headers.First(x => x.Key == "UserType").Value.First(); - var userId = context.Request.Headers.First(x => x.Key == "UserId").Value.First(); + _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => + { + 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 userType = context.Request.Headers.First(x => x.Key == "UserType").Value.First(); + var userId = context.Request.Headers.First(x => x.Key == "UserId").Value.First(); - var responseBody = $"CustomerId: {customerId} LocationId: {locationId} UserType: {userType} UserId: {userId}"; - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) - .Build(); - - _servicebuilder.Start(); + var responseBody = $"CustomerId: {customerId} LocationId: {locationId} UserType: {userType} UserId: {userId}"; + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); } private void GivenThereIsAnIdentityServerOn(string url, string apiName, AccessTokenType tokenType, TestUser user) @@ -203,7 +191,7 @@ namespace Ocelot.AcceptanceTests public void Dispose() { - _servicebuilder?.Dispose(); + _serviceHandler?.Dispose(); _steps.Dispose(); _identityServerBuilder?.Dispose(); } diff --git a/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs b/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs index 6963f20c..d7d31abe 100644 --- a/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs +++ b/test/Ocelot.AcceptanceTests/ClientRateLimitTests.cs @@ -1,36 +1,25 @@ -using Microsoft.AspNetCore.Builder; -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 +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 { - private IWebHost _builder; private readonly Steps _steps; - private int _counterOne; + private int _counterOne; + private readonly ServiceHandler _serviceHandler; public ClientRateLimitTests() { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); } - public void Dispose() - { - _builder?.Dispose(); - _steps.Dispose(); - } - [Fact] public void should_call_withratelimiting() { @@ -53,7 +42,6 @@ namespace Ocelot.AcceptanceTests UpstreamPathTemplate = "/api/ClientRateLimit", UpstreamHttpMethod = new List { "Get" }, RequestIdKey = _steps.RequestIdKey, - RateLimitOptions = new FileRateLimitRule() { EnableRateLimiting = true, @@ -158,6 +146,8 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_call_middleware_withWhitelistClient() { + int port = 61876; + var configuration = new FileConfiguration { ReRoutes = new List @@ -170,7 +160,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51876, + Port = port, } }, 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.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimesForRateLimit("/api/ClientRateLimit", 4)) @@ -211,26 +201,18 @@ namespace Ocelot.AcceptanceTests private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath) { - _builder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(baseUrl) - .Configure(app => - { - app.UsePathBase(basePath); - app.Run(context => - { - _counterOne++; - context.Response.StatusCode = 200; - context.Response.WriteAsync(_counterOne.ToString()); - return Task.CompletedTask; - }); - }) - .Build(); + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, context => + { + _counterOne++; + context.Response.StatusCode = 200; + context.Response.WriteAsync(_counterOne.ToString()); + return Task.CompletedTask; + }); + } - _builder.Start(); + public void Dispose() + { + _steps.Dispose(); } } } diff --git a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs b/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs deleted file mode 100644 index 5a5cce82..00000000 --- a/test/Ocelot.AcceptanceTests/ConfigurationInConsulTests.cs +++ /dev/null @@ -1,390 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Net; -using System.Text; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; -using static Ocelot.Infrastructure.Wait; - -namespace Ocelot.AcceptanceTests -{ - public class ConfigurationInConsulTests : IDisposable - { - private IWebHost _builder; - private readonly Steps _steps; - private IWebHost _fakeConsulBuilder; - private FileConfiguration _config; - - public ConfigurationInConsulTests() - { - _steps = new Steps(); - } - - [Fact] - public void should_return_response_200_with_simple_url() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51779, - } - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = 9500 - } - } - }; - - var fakeConsulServiceDiscoveryUrl = "http://localhost:9500"; - - this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_simple_url_when_using_jsonserialized_cache() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51779, - } - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = 9502 - } - } - }; - - var fakeConsulServiceDiscoveryUrl = "http://localhost:9502"; - - this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_load_configuration_out_of_consul() - { - var consulPort = 8500; - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - - var consulConfig = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/status", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51779, - } - }, - UpstreamPathTemplate = "/cs/status", - UpstreamHttpMethod = new List {"Get"} - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - this.Given(x => GivenTheConsulConfigurationIs(consulConfig)) - .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51779", "/status", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_load_configuration_out_of_consul_if_it_is_changed() - { - var consulPort = 8506; - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - - var consulConfig = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/status", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51780, - } - }, - UpstreamPathTemplate = "/cs/status", - UpstreamHttpMethod = new List {"Get"} - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - var secondConsulConfig = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/status", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51780, - } - }, - UpstreamPathTemplate = "/cs/status/awesome", - UpstreamHttpMethod = new List {"Get"} - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - this.Given(x => GivenTheConsulConfigurationIs(consulConfig)) - .And(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) - .And(x => x.GivenThereIsAServiceRunningOn("http://localhost:51780", "/status", 200, "Hello from Laura")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningUsingConsulToStoreConfig()) - .And(x => _steps.WhenIGetUrlOnTheApiGateway("/cs/status")) - .And(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .When(x => GivenTheConsulConfigurationIs(secondConsulConfig)) - .Then(x => ThenTheConfigIsUpdatedInOcelot()) - .BDDfy(); - } - - private void ThenTheConfigIsUpdatedInOcelot() - { - var result = WaitFor(20000).Until(() => { - try - { - _steps.WhenIGetUrlOnTheApiGateway("/cs/status/awesome"); - _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK); - _steps.ThenTheResponseBodyShouldBe("Hello from Laura"); - return true; - } - catch (Exception) - { - return false; - } - }); - result.ShouldBeTrue(); - } - - private void GivenTheConsulConfigurationIs(FileConfiguration config) - { - _config = config; - } - - private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) - { - _fakeConsulBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - if (context.Request.Method.ToLower() == "get" && context.Request.Path.Value == "/v1/kv/InternalConfiguration") - { - var json = JsonConvert.SerializeObject(_config); - - var bytes = Encoding.UTF8.GetBytes(json); - - var base64 = Convert.ToBase64String(bytes); - - var kvp = new FakeConsulGetResponse(base64); - - await context.Response.WriteJsonAsync(new FakeConsulGetResponse[] { kvp }); - } - else if (context.Request.Method.ToLower() == "put" && context.Request.Path.Value == "/v1/kv/InternalConfiguration") - { - try - { - var reader = new StreamReader(context.Request.Body); - - var json = reader.ReadToEnd(); - - _config = JsonConvert.DeserializeObject(json); - - var response = JsonConvert.SerializeObject(true); - - await context.Response.WriteAsync(response); - } - catch (Exception e) - { - Console.WriteLine(e); - throw; - } - } - }); - }) - .Build(); - - _fakeConsulBuilder.Start(); - } - - public class FakeConsulGetResponse - { - public FakeConsulGetResponse(string value) - { - Value = value; - } - - public int CreateIndex => 100; - public int ModifyIndex => 200; - public int LockIndex => 200; - public string Key => "InternalConfiguration"; - public int Flags => 0; - public string Value { get; private set; } - public string Session => "adf4238a-882b-9ddc-4a9d-5b6758e4159e"; - } - - private void GivenThereIsAServiceRunningOn(string url, string basePath, int statusCode, string responseBody) - { - _builder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.UsePathBase(basePath); - - app.Run(async context => - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) - .Build(); - - _builder.Start(); - } - - public void Dispose() - { - _builder?.Dispose(); - _steps.Dispose(); - } - } -} diff --git a/test/Ocelot.AcceptanceTests/ConfigurationReloadTests.cs b/test/Ocelot.AcceptanceTests/ConfigurationReloadTests.cs new file mode 100644 index 00000000..f5a23938 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/ConfigurationReloadTests.cs @@ -0,0 +1,68 @@ +using Ocelot.Configuration.File; +using Ocelot.Configuration.Setter; +using Ocelot.Middleware; +using Shouldly; +using System; +using System.Collections.Generic; +using System.Text; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.AcceptanceTests +{ + public class ConfigurationReloadTests : IDisposable + { + private FileConfiguration _initialConfig; + private FileConfiguration _anotherConfig; + private Steps _steps; + + public ConfigurationReloadTests() + { + _steps = new Steps(); + + _initialConfig = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + RequestIdKey = "initialKey" + } + }; + + _anotherConfig = new FileConfiguration + { + GlobalConfiguration = new FileGlobalConfiguration + { + RequestIdKey = "someOtherKey" + } + }; + } + + [Fact] + public void should_reload_config_on_change() + { + this.Given(x => _steps.GivenThereIsAConfiguration(_initialConfig)) + .And(x => _steps.GivenOcelotIsRunningReloadingConfig(true)) + .And(x => _steps.GivenThereIsAConfiguration(_anotherConfig)) + .And(x => _steps.GivenIWait(2500)) + .And(x => _steps.ThenConfigShouldBe(_anotherConfig)) + .BDDfy(); + } + + [Fact] + public void should_not_reload_config_on_change() + { + + this.Given(x => _steps.GivenThereIsAConfiguration(_initialConfig)) + .And(x => _steps.GivenOcelotIsRunningReloadingConfig(false)) + .And(x => _steps.GivenThereIsAConfiguration(_anotherConfig)) + .And(x => _steps.GivenIWait(2500)) + .And(x => _steps.ThenConfigShouldBe(_initialConfig)) + .BDDfy(); + } + + public void Dispose() + { + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/ContentTests.cs b/test/Ocelot.AcceptanceTests/ContentTests.cs new file mode 100644 index 00000000..b92b4a99 --- /dev/null +++ b/test/Ocelot.AcceptanceTests/ContentTests.cs @@ -0,0 +1,177 @@ +namespace Ocelot.AcceptanceTests +{ + using System; + using System.Collections.Generic; + using System.Net; + using Microsoft.AspNetCore.Http; + using Ocelot.Configuration.File; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class ContentTests : IDisposable + { + private readonly Steps _steps; + private string _contentType; + private long? _contentLength; + private bool _contentTypeHeaderExists; + private readonly ServiceHandler _serviceHandler; + + public ContentTests() + { + _serviceHandler = new ServiceHandler(); + _steps = new Steps(); + } + + [Fact] + public void should_not_add_content_type_or_content_length_headers() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51339, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51339", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) + .And(x => ThenTheContentTypeShouldBeEmpty()) + .And(x => ThenTheContentLengthShouldBeEmpty()) + .BDDfy(); + } + + [Fact] + public void should_add_content_type_and_content_length_headers() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51349, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Post" }, + } + } + }; + + var contentType = "application/json"; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51349", "/", 201, string.Empty)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenThePostHasContent("postContent")) + .And(x => _steps.GivenThePostHasContentType(contentType)) + .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created)) + .And(x => ThenTheContentLengthIs(11)) + .And(x => ThenTheContentTypeIsIs(contentType)) + .BDDfy(); + } + + [Fact] + public void should_add_default_content_type_header() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51359, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Post" }, + } + } + }; + + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51359", "/", 201, string.Empty)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenThePostHasContent("postContent")) + .When(x => _steps.WhenIPostUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.Created)) + .And(x => ThenTheContentLengthIs(11)) + .And(x => ThenTheContentTypeIsIs("text/plain; charset=utf-8")) + .BDDfy(); + } + + private void ThenTheContentTypeIsIs(string expected) + { + _contentType.ShouldBe(expected); + } + + private void ThenTheContentLengthShouldBeEmpty() + { + _contentLength.ShouldBeNull(); + } + + private void ThenTheContentLengthIs(int expected) + { + _contentLength.ShouldBe(expected); + } + + private void ThenTheContentTypeShouldBeEmpty() + { + _contentType.ShouldBeNullOrEmpty(); + _contentTypeHeaderExists.ShouldBe(false); + } + + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) + { + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => + { + _contentType = context.Request.ContentType; + _contentLength = context.Request.ContentLength; + _contentTypeHeaderExists = context.Request.Headers.TryGetValue("Content-Type", out var value); + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + } + + public void Dispose() + { + _serviceHandler?.Dispose(); + _steps.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs index 422348d0..7ed105c3 100644 --- a/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs +++ b/test/Ocelot.AcceptanceTests/CustomMiddlewareTests.cs @@ -1,29 +1,27 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Net; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration.File; -using Ocelot.Middleware; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.AcceptanceTests +namespace Ocelot.AcceptanceTests { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Net; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Ocelot.Configuration.File; + using Ocelot.Middleware; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + public class CustomMiddlewareTests : IDisposable { private readonly string _configurationPath; - private IWebHost _builder; private readonly Steps _steps; private int _counter; + private readonly ServiceHandler _serviceHandler; public CustomMiddlewareTests() { + _serviceHandler = new ServiceHandler(); _counter = 0; _steps = new Steps(); _configurationPath = "ocelot.json"; @@ -340,37 +338,24 @@ namespace Ocelot.AcceptanceTests private void GivenThereIsAServiceRunningOn(string url, int statusCode, string basePath) { - _builder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => + _serviceHandler.GivenThereIsAServiceRunningOn(url, context => + { + if (string.IsNullOrEmpty(basePath)) { - app.UsePathBase(basePath); - app.Run(context => - { - if(string.IsNullOrEmpty(basePath)) - { - context.Response.StatusCode = statusCode; - } - else if(context.Request.Path.Value != basePath) - { - context.Response.StatusCode = 404; - } + context.Response.StatusCode = statusCode; + } + else if (context.Request.Path.Value != basePath) + { + context.Response.StatusCode = 404; + } - return Task.CompletedTask; - }); - }) - .Build(); - - _builder.Start(); + return Task.CompletedTask; + }); } public void Dispose() { - _builder?.Dispose(); + _serviceHandler?.Dispose(); _steps.Dispose(); } diff --git a/test/Ocelot.AcceptanceTests/GzipTests.cs b/test/Ocelot.AcceptanceTests/GzipTests.cs index 8403c305..faa0c55d 100644 --- a/test/Ocelot.AcceptanceTests/GzipTests.cs +++ b/test/Ocelot.AcceptanceTests/GzipTests.cs @@ -1,29 +1,25 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - namespace Ocelot.AcceptanceTests { + using System; + using System.Collections.Generic; + using System.IO; + using System.IO.Compression; + using System.Linq; + using System.Net; + using Microsoft.AspNetCore.Http; + using Ocelot.Configuration.File; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + public class GzipTests : IDisposable { - private IWebHost _builder; private readonly Steps _steps; + private readonly ServiceHandler _serviceHandler; public GzipTests() { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); } @@ -66,51 +62,40 @@ namespace Ocelot.AcceptanceTests private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody, string expected) { - _builder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => + { + if (context.Request.Headers.TryGetValue("Content-Encoding", out var contentEncoding)) { - app.UsePathBase(basePath); - app.Run(async context => - { - if(context.Request.Headers.TryGetValue("Content-Encoding", out var contentEncoding)) - { - contentEncoding.First().ShouldBe("gzip"); - - string text = null; - using (var decompress = new GZipStream(context.Request.Body, CompressionMode.Decompress)) - { - using (var sr = new StreamReader(decompress)) { - text = sr.ReadToEnd(); - } - } + contentEncoding.First().ShouldBe("gzip"); - if(text != expected) - { - throw new Exception("not gzipped"); - } - - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - } - else + string text = null; + using (var decompress = new GZipStream(context.Request.Body, CompressionMode.Decompress)) + { + using (var sr = new StreamReader(decompress)) { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync("downstream path didnt match base path"); + text = sr.ReadToEnd(); } - }); - }) - .Build(); + } - _builder.Start(); + if (text != expected) + { + throw new Exception("not gzipped"); + } + + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + }); } public void Dispose() { - _builder?.Dispose(); + _serviceHandler?.Dispose(); _steps.Dispose(); } } diff --git a/test/Ocelot.AcceptanceTests/HeaderTests.cs b/test/Ocelot.AcceptanceTests/HeaderTests.cs index fd8b2767..c31c8868 100644 --- a/test/Ocelot.AcceptanceTests/HeaderTests.cs +++ b/test/Ocelot.AcceptanceTests/HeaderTests.cs @@ -1,26 +1,24 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration.File; -using TestStack.BDDfy; -using Xunit; - namespace Ocelot.AcceptanceTests { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Ocelot.Configuration.File; + using TestStack.BDDfy; + using Xunit; + public class HeaderTests : IDisposable { - private IWebHost _builder; private int _count; private readonly Steps _steps; + private readonly ServiceHandler _serviceHandler; public HeaderTests() { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); } @@ -313,99 +311,135 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } + [Fact] + public void issue_474_should_not_put_spaces_in_header() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Accept")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader("Accept", "text/html,application/xhtml+xml,application/xml;")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("text/html,application/xhtml+xml,application/xml;")) + .BDDfy(); + } + + [Fact] + public void issue_474_should_put_spaces_in_header() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 200, "Accept")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .And(x => _steps.GivenIAddAHeader("Accept", "text/html")) + .And(x => _steps.GivenIAddAHeader("Accept", "application/xhtml+xml")) + .And(x => _steps.GivenIAddAHeader("Accept", "application/xml")) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe("text/html, application/xhtml+xml, application/xml")) + .BDDfy(); + } + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode) { - _builder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, context => + { + if (_count == 0) { - app.UsePathBase(basePath); - app.Run(context => + context.Response.Cookies.Append("test", "0"); + _count++; + context.Response.StatusCode = statusCode; + return Task.CompletedTask; + } + + if (context.Request.Cookies.TryGetValue("test", out var cookieValue) || context.Request.Headers.TryGetValue("Set-Cookie", out var headerValue)) + { + if (cookieValue == "0" || headerValue == "test=1; path=/") { - if (_count == 0) - { - context.Response.Cookies.Append("test", "0"); - _count++; - context.Response.StatusCode = statusCode; - return Task.CompletedTask; - } - - if (context.Request.Cookies.TryGetValue("test", out var cookieValue) || context.Request.Headers.TryGetValue("Set-Cookie", out var headerValue)) - { - if (cookieValue == "0" || headerValue == "test=1; path=/") - { - context.Response.StatusCode = statusCode; - return Task.CompletedTask; - } - } - - context.Response.StatusCode = 500; + context.Response.StatusCode = statusCode; return Task.CompletedTask; - }); - }) - .Build(); + } + } - _builder.Start(); + context.Response.StatusCode = 500; + return Task.CompletedTask; + }); } private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string headerKey) { - _builder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => + { + if (context.Request.Headers.TryGetValue(headerKey, out var values)) { - app.UsePathBase(basePath); - app.Run(async context => - { - if (context.Request.Headers.TryGetValue(headerKey, out var values)) - { - var result = values.First(); - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(result); - } - }); - }) - .Build(); - - _builder.Start(); + var result = values.First(); + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(result); + } + }); } private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string headerKey, string headerValue) { - _builder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, context => + { + context.Response.OnStarting(() => { - app.UsePathBase(basePath); - app.Run(context => - { - context.Response.OnStarting(() => - { - context.Response.Headers.Add(headerKey, headerValue); - context.Response.StatusCode = statusCode; - return Task.CompletedTask; - }); + context.Response.Headers.Add(headerKey, headerValue); + context.Response.StatusCode = statusCode; + return Task.CompletedTask; + }); - return Task.CompletedTask; - }); - }) - .Build(); - - _builder.Start(); + return Task.CompletedTask; + }); } public void Dispose() { - _builder?.Dispose(); + _serviceHandler?.Dispose(); _steps.Dispose(); } } diff --git a/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs b/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs index a6a9a9ce..a508f1d4 100644 --- a/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs +++ b/test/Ocelot.AcceptanceTests/HttpDelegatingHandlersTests.cs @@ -1,28 +1,26 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.AcceptanceTests +namespace Ocelot.AcceptanceTests { - public class HttpDelegatingHandlersTests + using System; + using System.Collections.Generic; + using System.Net; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Ocelot.Configuration.File; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class HttpDelegatingHandlersTests : IDisposable { - private IWebHost _builder; private readonly Steps _steps; private string _downstreamPath; + private readonly ServiceHandler _serviceHandler; public HttpDelegatingHandlersTests() { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); } @@ -249,6 +247,7 @@ namespace Ocelot.AcceptanceTests return base.SendAsync(request, cancellationToken); } } + // ReSharper disable once ClassNeverInstantiated.Local private class FakeHandlerAgain : DelegatingHandler { @@ -263,33 +262,27 @@ namespace Ocelot.AcceptanceTests private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) { - _builder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => + { + _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; + + if (_downstreamPath != basePath) { - app.UsePathBase(basePath); - app.Run(async context => - { - _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + }); + } - if (_downstreamPath != basePath) - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync("downstream path didnt match base path"); - } - else - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - } - }); - }) - .Build(); - - _builder.Start(); + public void Dispose() + { + _steps?.Dispose(); + _serviceHandler?.Dispose(); } } } diff --git a/test/Ocelot.AcceptanceTests/LoadBalancerTests.cs b/test/Ocelot.AcceptanceTests/LoadBalancerTests.cs index 567271a0..e6832efc 100644 --- a/test/Ocelot.AcceptanceTests/LoadBalancerTests.cs +++ b/test/Ocelot.AcceptanceTests/LoadBalancerTests.cs @@ -1,36 +1,36 @@ -using System; -using System.Collections.Generic; -using System.IO; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration.File; -using Ocelot.LoadBalancer.LoadBalancers; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - namespace Ocelot.AcceptanceTests { + using System; + using System.Collections.Generic; + using Microsoft.AspNetCore.Http; + using Ocelot.Configuration.File; + using Ocelot.LoadBalancer.LoadBalancers; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + public class LoadBalancerTests : IDisposable { - private IWebHost _builderOne; - private IWebHost _builderTwo; private readonly Steps _steps; private int _counterOne; private int _counterTwo; private static readonly object _syncLock = new object(); + private readonly ServiceHandler _serviceHandler; public LoadBalancerTests() { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); } [Fact] public void should_load_balance_request_with_least_connection() { - var downstreamServiceOneUrl = "http://localhost:50881"; - var downstreamServiceTwoUrl = "http://localhost:50892"; + int portOne = 50591; + int portTwo = 51482; + + var downstreamServiceOneUrl = $"http://localhost:{portOne}"; + var downstreamServiceTwoUrl = $"http://localhost:{portTwo}"; var configuration = new FileConfiguration { @@ -48,12 +48,12 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 50881 + Port = portOne }, new FileHostAndPort { Host = "localhost", - Port = 50892 + Port = portTwo } } } @@ -76,8 +76,8 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_load_balance_request_with_round_robin() { - var downstreamPortOne = 51881; - var downstreamPortTwo = 51892; + var downstreamPortOne = 51701; + var downstreamPortTwo = 53802; var downstreamServiceOneUrl = $"http://localhost:{downstreamPortOne}"; var downstreamServiceTwoUrl = $"http://localhost:{downstreamPortTwo}"; @@ -136,77 +136,53 @@ namespace Ocelot.AcceptanceTests private void GivenProductServiceOneIsRunning(string url, int statusCode) { - _builderOne = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => + _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => + { + try { - app.Run(async context => + var response = string.Empty; + lock (_syncLock) { - try - { - var response = string.Empty; - lock (_syncLock) - { - _counterOne++; - response = _counterOne.ToString(); - } - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(response); - } - catch (Exception exception) - { - await context.Response.WriteAsync(exception.StackTrace); - } - }); - }) - .Build(); + _counterOne++; + response = _counterOne.ToString(); + } - _builderOne.Start(); + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(response); + } + catch (Exception exception) + { + await context.Response.WriteAsync(exception.StackTrace); + } + }); } private void GivenProductServiceTwoIsRunning(string url, int statusCode) { - _builderTwo = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => + _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => + { + try { - app.Run(async context => + var response = string.Empty; + lock (_syncLock) { - try - { - var response = string.Empty; - lock (_syncLock) - { - _counterTwo++; - response = _counterTwo.ToString(); - } - - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(response); - } - catch (System.Exception exception) - { - await context.Response.WriteAsync(exception.StackTrace); - } - }); - }) - .Build(); + _counterTwo++; + response = _counterTwo.ToString(); + } - _builderTwo.Start(); + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(response); + } + catch (Exception exception) + { + await context.Response.WriteAsync(exception.StackTrace); + } + }); } public void Dispose() { - _builderOne?.Dispose(); - _builderTwo?.Dispose(); + _serviceHandler?.Dispose(); _steps.Dispose(); } } diff --git a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj index c4e902fd..15d5e2b3 100644 --- a/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj +++ b/test/Ocelot.AcceptanceTests/Ocelot.AcceptanceTests.csproj @@ -32,7 +32,6 @@ - @@ -51,9 +50,9 @@ - - - + + + diff --git a/test/Ocelot.AcceptanceTests/QoSTests.cs b/test/Ocelot.AcceptanceTests/QoSTests.cs index 0a9a110a..e62de7b3 100644 --- a/test/Ocelot.AcceptanceTests/QoSTests.cs +++ b/test/Ocelot.AcceptanceTests/QoSTests.cs @@ -1,27 +1,24 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -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 System.Threading; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Ocelot.Configuration.File; + using TestStack.BDDfy; + using Xunit; + public class QoSTests : IDisposable { - private IWebHost _brokenService; private readonly Steps _steps; private int _requestCount; - private IWebHost _workingService; + private readonly ServiceHandler _serviceHandler; public QoSTests() { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); } @@ -227,74 +224,48 @@ namespace Ocelot.AcceptanceTests private void GivenThereIsAPossiblyBrokenServiceRunningOn(string url, string responseBody) { - _brokenService = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => + _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => + { + //circuit starts closed + if (_requestCount == 0) { - app.Run(async context => - { - //circuit starts closed - if (_requestCount == 0) - { - _requestCount++; - context.Response.StatusCode = 200; - await context.Response.WriteAsync(responseBody); - return; - } + _requestCount++; + context.Response.StatusCode = 200; + await context.Response.WriteAsync(responseBody); + return; + } - //request one times out and polly throws exception, circuit opens - if (_requestCount == 1) - { - _requestCount++; - await Task.Delay(1000); - context.Response.StatusCode = 200; - return; - } + //request one times out and polly throws exception, circuit opens + if (_requestCount == 1) + { + _requestCount++; + await Task.Delay(1000); + context.Response.StatusCode = 200; + return; + } - //after break closes we return 200 OK - if (_requestCount == 2) - { - context.Response.StatusCode = 200; - await context.Response.WriteAsync(responseBody); - return; - } - }); - }) - .Build(); - - _brokenService.Start(); + //after break closes we return 200 OK + if (_requestCount == 2) + { + context.Response.StatusCode = 200; + await context.Response.WriteAsync(responseBody); + } + }); } private void GivenThereIsAServiceRunningOn(string url, int statusCode, string responseBody, int timeout) { - _workingService = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - Thread.Sleep(timeout); - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) - .Build(); - - _workingService.Start(); + _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => + { + Thread.Sleep(timeout); + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); } public void Dispose() { - _workingService?.Dispose(); - _brokenService?.Dispose(); + _serviceHandler?.Dispose(); _steps.Dispose(); } } diff --git a/test/Ocelot.AcceptanceTests/RequestIdTests.cs b/test/Ocelot.AcceptanceTests/RequestIdTests.cs index 7989dc90..6acc36bd 100644 --- a/test/Ocelot.AcceptanceTests/RequestIdTests.cs +++ b/test/Ocelot.AcceptanceTests/RequestIdTests.cs @@ -1,26 +1,21 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Primitives; -using Ocelot.Configuration.File; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.AcceptanceTests +namespace Ocelot.AcceptanceTests { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Ocelot.Configuration.File; + using TestStack.BDDfy; + using Xunit; + public class RequestIdTests : IDisposable { - private IWebHost _builder; private readonly Steps _steps; + private readonly ServiceHandler _serviceHandler; public RequestIdTests() { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); } @@ -171,30 +166,17 @@ namespace Ocelot.AcceptanceTests private void GivenThereIsAServiceRunningOn(string url) { - _builder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(context => - { - StringValues requestId; - context.Request.Headers.TryGetValue(_steps.RequestIdKey, out requestId); - context.Response.Headers.Add(_steps.RequestIdKey, requestId.First()); - return Task.CompletedTask; - }); - }) - .Build(); - - _builder.Start(); + _serviceHandler.GivenThereIsAServiceRunningOn(url, context => + { + context.Request.Headers.TryGetValue(_steps.RequestIdKey, out var requestId); + context.Response.Headers.Add(_steps.RequestIdKey, requestId.First()); + return Task.CompletedTask; + }); } public void Dispose() { - _builder?.Dispose(); + _serviceHandler?.Dispose(); _steps.Dispose(); } } diff --git a/test/Ocelot.AcceptanceTests/ResponseCodeTests.cs b/test/Ocelot.AcceptanceTests/ResponseCodeTests.cs index e4a361e4..244511c4 100644 --- a/test/Ocelot.AcceptanceTests/ResponseCodeTests.cs +++ b/test/Ocelot.AcceptanceTests/ResponseCodeTests.cs @@ -1,25 +1,20 @@ -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; - namespace Ocelot.AcceptanceTests { + using System; + using System.Collections.Generic; + using System.Net; + using Ocelot.Configuration.File; + using TestStack.BDDfy; + using Xunit; + public class ResponseCodeTests : IDisposable { - private IWebHost _builder; private readonly Steps _steps; - private string _downstreamPath; + private readonly ServiceHandler _serviceHandler; public ResponseCodeTests() { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); } @@ -58,26 +53,15 @@ namespace Ocelot.AcceptanceTests private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode) { - _builder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => - { - app.UsePathBase(basePath); - app.Run(async context => - { - context.Response.StatusCode = statusCode; - }); - }) - .Build(); - - _builder.Start(); + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => + { + context.Response.StatusCode = statusCode; + }); } + public void Dispose() { - _builder?.Dispose(); + _serviceHandler?.Dispose(); _steps.Dispose(); } } diff --git a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs index 72569b97..26ccd1cd 100644 --- a/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs +++ b/test/Ocelot.AcceptanceTests/ReturnsErrorTests.cs @@ -1,22 +1,20 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -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 Ocelot.Configuration.File; + using TestStack.BDDfy; + using Xunit; + public class ReturnsErrorTests : IDisposable { - private IWebHost _servicebuilder; private readonly Steps _steps; + private readonly ServiceHandler _serviceHandler; public ReturnsErrorTests() { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); } @@ -55,27 +53,12 @@ namespace Ocelot.AcceptanceTests private void GivenThereIsAServiceRunningOn(string url) { - _servicebuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(context => - { - throw new Exception("BLAMMMM"); - }); - }) - .Build(); - - _servicebuilder.Start(); + _serviceHandler.GivenThereIsAServiceRunningOn(url, context => throw new Exception("BLAMMMM")); } public void Dispose() { - _servicebuilder?.Dispose(); + _serviceHandler?.Dispose(); _steps.Dispose(); } } diff --git a/test/Ocelot.AcceptanceTests/RoutingTests.cs b/test/Ocelot.AcceptanceTests/RoutingTests.cs index 38ebe18e..340db66f 100644 --- a/test/Ocelot.AcceptanceTests/RoutingTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingTests.cs @@ -1,25 +1,23 @@ -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; - namespace Ocelot.AcceptanceTests { + using System; + using System.Collections.Generic; + using System.Net; + using Microsoft.AspNetCore.Http; + using Ocelot.Configuration.File; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + public class RoutingTests : IDisposable { - private IWebHost _builder; private readonly Steps _steps; private string _downstreamPath; + private readonly ServiceHandler _serviceHandler; public RoutingTests() { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); } @@ -973,33 +971,21 @@ namespace Ocelot.AcceptanceTests private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) { - _builder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => - { - app.UsePathBase(basePath); - app.Run(async context => - { - _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; - - if(_downstreamPath != basePath) - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync("downstream path didnt match base path"); - } - else - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - } - }); - }) - .Build(); + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => + { + _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; - _builder.Start(); + if (_downstreamPath != basePath) + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + }); } internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPath) @@ -1009,7 +995,7 @@ namespace Ocelot.AcceptanceTests public void Dispose() { - _builder?.Dispose(); + _serviceHandler.Dispose(); _steps.Dispose(); } } diff --git a/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs b/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs index f97340af..46919200 100644 --- a/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs +++ b/test/Ocelot.AcceptanceTests/RoutingWithQueryStringTests.cs @@ -1,25 +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 Shouldly; -using TestStack.BDDfy; -using Xunit; - 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 RoutingWithQueryStringTests : IDisposable { - private IWebHost _builder; private readonly Steps _steps; - private string _downstreamPath; + private readonly ServiceHandler _serviceHandler; public RoutingWithQueryStringTests() { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); } @@ -208,41 +204,24 @@ namespace Ocelot.AcceptanceTests private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, string queryString, int statusCode, string responseBody) { - _builder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => + { + if (context.Request.PathBase.Value != basePath || context.Request.QueryString.Value != queryString) { - app.UsePathBase(basePath); - app.Run(async context => - { - if(context.Request.PathBase.Value != basePath || context.Request.QueryString.Value != queryString) - { - context.Response.StatusCode = 500; - await context.Response.WriteAsync("downstream path didnt match base path"); - } - else - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - } - }); - }) - .Build(); - - _builder.Start(); - } - - internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPath) - { - _downstreamPath.ShouldBe(expectedDownstreamPath); + context.Response.StatusCode = 500; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + }); } public void Dispose() { - _builder?.Dispose(); + _serviceHandler?.Dispose(); _steps.Dispose(); } } diff --git a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs b/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs deleted file mode 100644 index 9d21aa34..00000000 --- a/test/Ocelot.AcceptanceTests/ServiceDiscoveryTests.cs +++ /dev/null @@ -1,923 +0,0 @@ -namespace Ocelot.AcceptanceTests -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Net; - using Consul; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Http; - using Ocelot.Configuration.File; - using Shouldly; - using TestStack.BDDfy; - using Xunit; - using Newtonsoft.Json; - using Steeltoe.Common.Discovery; - - public class ServiceDiscoveryTests : IDisposable - { - private IWebHost _builderOne; - private IWebHost _builderTwo; - private IWebHost _fakeConsulBuilder; - private readonly Steps _steps; - private readonly List _consulServices; - private readonly List _eurekaInstances; - private int _counterOne; - private int _counterTwo; - private static readonly object SyncLock = new object(); - private IWebHost _builder; - private string _downstreamPath; - private string _receivedToken; - - public ServiceDiscoveryTests() - { - _steps = new Steps(); - _consulServices = new List(); - _eurekaInstances = new List(); - } - - [Fact] - public void should_use_eureka_service_discovery_and_make_request() - { - var eurekaPort = 8761; - var serviceName = "product"; - var downstreamServicePort = 50371; - var downstreamServiceOneUrl = $"http://localhost:{downstreamServicePort}"; - var fakeEurekaServiceDiscoveryUrl = $"http://localhost:{eurekaPort}"; - - var instanceOne = new FakeEurekaService(serviceName, "localhost", downstreamServicePort, false, - new Uri($"http://localhost:{downstreamServicePort}"), new Dictionary()); - - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - ServiceName = serviceName, - LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }, - UseServiceDiscovery = true, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Type = "Eureka" - } - } - }; - - this.Given(x => x.GivenEurekaProductServiceOneIsRunning(downstreamServiceOneUrl, 200)) - .And(x => x.GivenThereIsAFakeEurekaServiceDiscoveryProvider(fakeEurekaServiceDiscoveryUrl, serviceName)) - .And(x => x.GivenTheServicesAreRegisteredWithEureka(instanceOne)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(_ => _steps.ThenTheResponseBodyShouldBe(nameof(ServiceDiscoveryTests))) - .BDDfy(); - } - - [Fact] - public void should_use_consul_service_discovery_and_load_balance_request() - { - var consulPort = 8502; - var serviceName = "product"; - var downstreamServiceOneUrl = "http://localhost:50881"; - var downstreamServiceTwoUrl = "http://localhost:50882"; - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = "localhost", - Port = 50881, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - var serviceEntryTwo = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = "localhost", - Port = 50882, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - ServiceName = serviceName, - LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }, - UseServiceDiscovery = true, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200)) - .And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200)) - .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) - .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 50)) - .Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50)) - .And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(24, 26)) - .BDDfy(); - } - - [Fact] - public void should_handle_request_to_consul_for_downstream_service_and_make_request() - { - const int consulPort = 8505; - const string serviceName = "web"; - const string downstreamServiceOneUrl = "http://localhost:8080"; - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = "localhost", - Port = 8080, - ID = "web_90_0_2_224_8080", - Tags = new[] {"version-v1"} - }, - }; - - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/home", - DownstreamScheme = "http", - UpstreamPathTemplate = "/home", - UpstreamHttpMethod = new List { "Get", "Options" }, - ServiceName = serviceName, - LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }, - UseServiceDiscovery = true, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/api/home", 200, "Hello from Laura")) - .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) - .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/home")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_handle_request_to_consul_for_downstream_service_and_make_request_no_re_routes() - { - const int consulPort = 8513; - const string serviceName = "web"; - const int downstreamServicePort = 8087; - var downstreamServiceOneUrl = $"http://localhost:{downstreamServicePort}"; - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = "localhost", - Port = downstreamServicePort, - ID = "web_90_0_2_224_8080", - Tags = new[] {"version-v1"} - }, - }; - - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider - { - Host = "localhost", - Port = consulPort - }, - DownstreamScheme = "http", - HttpHandlerOptions = new FileHttpHandlerOptions - { - AllowAutoRedirect = true, - UseCookieContainer = true, - UseTracing = false - }, - QoSOptions = new FileQoSOptions - { - TimeoutValue = 100, - DurationOfBreak = 1000, - ExceptionsAllowedBeforeBreaking = 1 - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/something", 200, "Hello from Laura")) - .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) - .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/web/something")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - [Fact] - public void should_use_consul_service_discovery_and_load_balance_request_no_re_routes() - { - var consulPort = 8510; - var serviceName = "product"; - var serviceOnePort = 50888; - var serviceTwoPort = 50889; - var downstreamServiceOneUrl = $"http://localhost:{serviceOnePort}"; - var downstreamServiceTwoUrl = $"http://localhost:{serviceTwoPort}"; - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = "localhost", - Port = serviceOnePort, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - var serviceEntryTwo = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = "localhost", - Port = serviceTwoPort, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - }, - LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }, - DownstreamScheme = "http" - } - }; - - this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200)) - .And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200)) - .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) - .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes($"/{serviceName}/", 50)) - .Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(50)) - .And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(24, 26)) - .BDDfy(); - } - - [Fact] - public void should_use_token_to_make_request_to_consul() - { - var token = "abctoken"; - var consulPort = 8515; - var serviceName = "web"; - var downstreamServiceOneUrl = "http://localhost:8081"; - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = "localhost", - Port = 8081, - ID = "web_90_0_2_224_8080", - Tags = new[] { "version-v1" } - }, - }; - - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/home", - DownstreamScheme = "http", - UpstreamPathTemplate = "/home", - UpstreamHttpMethod = new List { "Get", "Options" }, - ServiceName = serviceName, - LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }, - UseServiceDiscovery = true, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort, - Token = token - } - } - }; - - this.Given(_ => GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/api/home", 200, "Hello from Laura")) - .And(_ => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) - .And(_ => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne)) - .And(_ => _steps.GivenThereIsAConfiguration(configuration)) - .And(_ => _steps.GivenOcelotIsRunning()) - .When(_ => _steps.WhenIGetUrlOnTheApiGateway("/home")) - .Then(_ => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(_ => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .And(_ => _receivedToken.ShouldBe(token)) - .BDDfy(); - } - - [Fact] - public void should_send_request_to_service_after_it_becomes_available_in_consul() - { - var consulPort = 8501; - var serviceName = "product"; - var downstreamServiceOneUrl = "http://localhost:50879"; - var downstreamServiceTwoUrl = "http://localhost:50880"; - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = "localhost", - Port = 50879, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - var serviceEntryTwo = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = "localhost", - Port = 50880, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - ServiceName = serviceName, - LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }, - UseServiceDiscovery = true, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, 200)) - .And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, 200)) - .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) - .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .And(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 10)) - .And(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(10)) - .And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(4, 6)) - .And(x => WhenIRemoveAService(serviceEntryTwo)) - .And(x => GivenIResetCounters()) - .And(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 10)) - .And(x => ThenOnlyOneServiceHasBeenCalled()) - .And(x => WhenIAddAServiceBackIn(serviceEntryTwo)) - .And(x => GivenIResetCounters()) - .When(x => _steps.WhenIGetUrlOnTheApiGatewayMultipleTimes("/", 10)) - .Then(x => x.ThenTheTwoServicesShouldHaveBeenCalledTimes(10)) - .And(x => x.ThenBothServicesCalledRealisticAmountOfTimes(4, 6)) - .BDDfy(); - } - - [Fact] - public void should_handle_request_to_poll_consul_for_downstream_service_and_make_request() - { - const int consulPort = 8518; - const string serviceName = "web"; - const int downstreamServicePort = 8082; - var downstreamServiceOneUrl = $"http://localhost:{downstreamServicePort}"; - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = "localhost", - Port = downstreamServicePort, - ID = $"web_90_0_2_224_{downstreamServicePort}", - Tags = new[] {"version-v1"} - }, - }; - - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/home", - DownstreamScheme = "http", - UpstreamPathTemplate = "/home", - UpstreamHttpMethod = new List { "Get", "Options" }, - ServiceName = serviceName, - LoadBalancerOptions = new FileLoadBalancerOptions { Type = "LeastConnection" }, - UseServiceDiscovery = true, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort, - Type = "PollConsul", - PollingInterval = 0 - } - } - }; - - this.Given(x => x.GivenThereIsAServiceRunningOn(downstreamServiceOneUrl, "/api/home", 200, "Hello from Laura")) - .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) - .And(x => x.GivenTheServicesAreRegisteredWithConsul(serviceEntryOne)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGatewayWaitingForTheResponseToBeOk("/home")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("Hello from Laura")) - .BDDfy(); - } - - private void WhenIAddAServiceBackIn(ServiceEntry serviceEntryTwo) - { - _consulServices.Add(serviceEntryTwo); - } - - private void ThenOnlyOneServiceHasBeenCalled() - { - _counterOne.ShouldBe(10); - _counterTwo.ShouldBe(0); - } - - private void WhenIRemoveAService(ServiceEntry serviceEntryTwo) - { - _consulServices.Remove(serviceEntryTwo); - } - - private void GivenIResetCounters() - { - _counterOne = 0; - _counterTwo = 0; - } - - private void ThenBothServicesCalledRealisticAmountOfTimes(int bottom, int top) - { - _counterOne.ShouldBeInRange(bottom, top); - _counterOne.ShouldBeInRange(bottom, top); - } - - private void ThenTheTwoServicesShouldHaveBeenCalledTimes(int expected) - { - var total = _counterOne + _counterTwo; - total.ShouldBe(expected); - } - - private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries) - { - foreach(var serviceEntry in serviceEntries) - { - _consulServices.Add(serviceEntry); - } - } - - private void GivenTheServicesAreRegisteredWithEureka(params IServiceInstance[] serviceInstances) - { - foreach (var instance in serviceInstances) - { - _eurekaInstances.Add(instance); - } - } - - private void GivenThereIsAFakeEurekaServiceDiscoveryProvider(string url, string serviceName) - { - _fakeConsulBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - if (context.Request.Path.Value == "/eureka/apps/") - { - var apps = new List(); - - foreach (var serviceInstance in _eurekaInstances) - { - var a = new Application - { - name = serviceName, - instance = new List - { - new Instance - { - instanceId = $"{serviceInstance.Host}:{serviceInstance}", - hostName = serviceInstance.Host, - app = serviceName, - ipAddr = "127.0.0.1", - status = "UP", - overriddenstatus = "UNKNOWN", - port = new Port {value = serviceInstance.Port, enabled = "true"}, - securePort = new SecurePort {value = serviceInstance.Port, enabled = "true"}, - countryId = 1, - dataCenterInfo = new DataCenterInfo {value = "com.netflix.appinfo.InstanceInfo$DefaultDataCenterInfo", name = "MyOwn"}, - leaseInfo = new LeaseInfo - { - renewalIntervalInSecs = 30, - durationInSecs = 90, - registrationTimestamp = 1457714988223, - lastRenewalTimestamp= 1457716158319, - evictionTimestamp = 0, - serviceUpTimestamp = 1457714988223 - }, - metadata = new Metadata - { - value = "java.util.Collections$EmptyMap" - }, - homePageUrl = $"{serviceInstance.Host}:{serviceInstance.Port}", - statusPageUrl = $"{serviceInstance.Host}:{serviceInstance.Port}", - healthCheckUrl = $"{serviceInstance.Host}:{serviceInstance.Port}", - vipAddress = serviceName, - isCoordinatingDiscoveryServer = "false", - lastUpdatedTimestamp = "1457714988223", - lastDirtyTimestamp = "1457714988172", - actionType = "ADDED" - } - } - }; - - apps.Add(a); - } - - var applications = new EurekaApplications - { - applications = new Applications - { - application = apps, - apps__hashcode = "UP_1_", - versions__delta = "1" - } - }; - - await context.Response.WriteJsonAsync(applications); - } - }); - }) - .Build(); - - _fakeConsulBuilder.Start(); - } - - private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName) - { - _fakeConsulBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - if(context.Request.Path.Value == $"/v1/health/service/{serviceName}") - { - if (context.Request.Headers.TryGetValue("X-Consul-Token", out var values)) - { - _receivedToken = values.First(); - } - - await context.Response.WriteJsonAsync(_consulServices); - } - }); - }) - .Build(); - - _fakeConsulBuilder.Start(); - } - - private void GivenProductServiceOneIsRunning(string url, int statusCode) - { - _builderOne = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - try - { - string response; - lock (SyncLock) - { - _counterOne++; - response = _counterOne.ToString(); - } - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(response); - } - catch (Exception exception) - { - await context.Response.WriteAsync(exception.StackTrace); - } - }); - }) - .Build(); - - _builderOne.Start(); - } - - private void GivenProductServiceTwoIsRunning(string url, int statusCode) - { - _builderTwo = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - try - { - string response; - lock (SyncLock) - { - _counterTwo++; - response = _counterTwo.ToString(); - } - - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(response); - } - catch (Exception exception) - { - await context.Response.WriteAsync(exception.StackTrace); - } - }); - }) - .Build(); - - _builderTwo.Start(); - } - - private void GivenEurekaProductServiceOneIsRunning(string url, int statusCode) - { - _builderOne = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - try - { - context.Response.StatusCode = 200; - await context.Response.WriteAsync(nameof(ServiceDiscoveryTests)); - } - catch (Exception exception) - { - await context.Response.WriteAsync(exception.StackTrace); - } - }); - }) - .Build(); - - _builderOne.Start(); - } - - private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) - { - _builder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => - { - app.UsePathBase(basePath); - app.Run(async context => - { - _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; - - if(_downstreamPath != basePath) - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync("downstream path didnt match base path"); - } - else - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - } - }); - }) - .Build(); - - _builder.Start(); - } - - public void Dispose() - { - _builderOne?.Dispose(); - _builderTwo?.Dispose(); - _steps.Dispose(); - } - } - - public class FakeEurekaService : IServiceInstance - { - public FakeEurekaService(string serviceId, string host, int port, bool isSecure, Uri uri, IDictionary metadata) - { - ServiceId = serviceId; - Host = host; - Port = port; - IsSecure = isSecure; - Uri = uri; - Metadata = metadata; - } - - public string ServiceId { get; } - public string Host { get; } - public int Port { get; } - public bool IsSecure { get; } - public Uri Uri { get; } - public IDictionary Metadata { get; } - } - - public class Port - { - [JsonProperty("$")] - public int value { get; set; } - - [JsonProperty("@enabled")] - public string enabled { get; set; } - } - - public class SecurePort - { - [JsonProperty("$")] - public int value { get; set; } - - [JsonProperty("@enabled")] - public string enabled { get; set; } - } - - public class DataCenterInfo - { - [JsonProperty("@class")] - public string value { get; set; } - - public string name { get; set; } - } - - public class LeaseInfo - { - public int renewalIntervalInSecs { get; set; } - - public int durationInSecs { get; set; } - - public long registrationTimestamp { get; set; } - - public long lastRenewalTimestamp { get; set; } - - public int evictionTimestamp { get; set; } - - public long serviceUpTimestamp { get; set; } - } - - public class Metadata - { - [JsonProperty("@class")] - public string value { get; set; } - } - - public class Instance - { - public string instanceId { get; set; } - public string hostName { get; set; } - public string app { get; set; } - public string ipAddr { get; set; } - public string status { get; set; } - public string overriddenstatus { get; set; } - public Port port { get; set; } - public SecurePort securePort { get; set; } - public int countryId { get; set; } - public DataCenterInfo dataCenterInfo { get; set; } - public LeaseInfo leaseInfo { get; set; } - public Metadata metadata { get; set; } - public string homePageUrl { get; set; } - public string statusPageUrl { get; set; } - public string healthCheckUrl { get; set; } - public string vipAddress { get; set; } - public string isCoordinatingDiscoveryServer { get; set; } - public string lastUpdatedTimestamp { get; set; } - public string lastDirtyTimestamp { get; set; } - public string actionType { get; set; } - } - - public class Application - { - public string name { get; set; } - public List instance { get; set; } - } - - public class Applications - { - public string versions__delta { get; set; } - public string apps__hashcode { get; set; } - public List application { get; set; } - } - - public class EurekaApplications - { - public Applications applications { get; set; } - } -} diff --git a/test/Ocelot.AcceptanceTests/ServiceFabricTests.cs b/test/Ocelot.AcceptanceTests/ServiceFabricTests.cs index 3f79a801..e4c2abc4 100644 --- a/test/Ocelot.AcceptanceTests/ServiceFabricTests.cs +++ b/test/Ocelot.AcceptanceTests/ServiceFabricTests.cs @@ -1,28 +1,22 @@ -using System.Linq; -using Microsoft.Extensions.Primitives; - namespace Ocelot.AcceptanceTests { 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; public class ServiceFabricTests : IDisposable { - private IWebHost _builder; private readonly Steps _steps; private string _downstreamPath; + private readonly ServiceHandler _serviceHandler; public ServiceFabricTests() { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); } @@ -102,46 +96,34 @@ namespace Ocelot.AcceptanceTests private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody, string expectedQueryString) { - _builder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => + { + _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; + + if (_downstreamPath != basePath) { - app.UsePathBase(basePath); - app.Run(async context => - { - _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; - - if(_downstreamPath != basePath) - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync("downstream path didnt match base path"); - } - else - { - if (context.Request.QueryString.Value.Contains(expectedQueryString)) - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - } - else - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync("downstream path didnt match base path"); - } - } - }); - }) - .Build(); - - _builder.Start(); + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + if (context.Request.QueryString.Value.Contains(expectedQueryString)) + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + } + }); } public void Dispose() { - _builder?.Dispose(); + _serviceHandler?.Dispose(); _steps.Dispose(); } } diff --git a/test/Ocelot.AcceptanceTests/ServiceHandler.cs b/test/Ocelot.AcceptanceTests/ServiceHandler.cs new file mode 100644 index 00000000..6af5ba6a --- /dev/null +++ b/test/Ocelot.AcceptanceTests/ServiceHandler.cs @@ -0,0 +1,108 @@ +namespace Ocelot.AcceptanceTests +{ + using System; + using System.IO; + using System.Net; + using System.Net.WebSockets; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.Logging; + + public class ServiceHandler : IDisposable + { + private IWebHost _builder; + + public void GivenThereIsAServiceRunningOn(string baseUrl, RequestDelegate del) + { + _builder = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.Run(del); + }) + .Build(); + + _builder.Start(); + } + + public void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, RequestDelegate del) + { + _builder = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(del); + }) + .Build(); + + _builder.Start(); + } + + public void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, string fileName, string password, int port, RequestDelegate del) + { + _builder = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel(options => + { + options.Listen(IPAddress.Loopback, port, listenOptions => + { + listenOptions.UseHttps(fileName, password); + }); + }) + .UseContentRoot(Directory.GetCurrentDirectory()) + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(del); + }) + .Build(); + + _builder.Start(); + } + + public async Task StartFakeDownstreamService(string url, string path, Func, Task> middleware) + { + _builder = new WebHostBuilder() + .ConfigureServices(s => { }).UseKestrel() + .UseUrls(url) + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddEnvironmentVariables(); + }) + .ConfigureLogging((hostingContext, logging) => + { + logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + logging.AddConsole(); + }) + .Configure(app => + { + app.UseWebSockets(); + app.Use(middleware); + }) + .UseIISIntegration() + .Build(); + + await _builder.StartAsync(); + } + + public void Dispose() + { + _builder?.Dispose(); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/SslTests.cs b/test/Ocelot.AcceptanceTests/SslTests.cs index be753b46..e0ee5977 100644 --- a/test/Ocelot.AcceptanceTests/SslTests.cs +++ b/test/Ocelot.AcceptanceTests/SslTests.cs @@ -1,25 +1,22 @@ -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; - 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 SslTests : IDisposable { - private IWebHost _builder; private readonly Steps _steps; private string _downstreamPath; + private readonly ServiceHandler _serviceHandler; public SslTests() { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); } @@ -98,48 +95,26 @@ namespace Ocelot.AcceptanceTests private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody, int port) { - _builder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel(options => + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, "idsrv3test.pfx", "idsrv3test", port, async context => + { + _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; + + if (_downstreamPath != basePath) { - options.Listen(IPAddress.Loopback, port, listenOptions => - { - listenOptions.UseHttps("idsrv3test.pfx", "idsrv3test"); - }); - }) - .UseContentRoot(Directory.GetCurrentDirectory()) - .Configure(app => + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else { - app.UsePathBase(basePath); - app.Run(async context => - { - _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; - - if(_downstreamPath != basePath) - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync("downstream path didnt match base path"); - } - else - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - } - }); - }) - .Build(); - - _builder.Start(); - } - - internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPath) - { - _downstreamPath.ShouldBe(expectedDownstreamPath); + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + }); } public void Dispose() { - _builder?.Dispose(); + _serviceHandler?.Dispose(); _steps.Dispose(); } } diff --git a/test/Ocelot.AcceptanceTests/StartupTests.cs b/test/Ocelot.AcceptanceTests/StartupTests.cs new file mode 100644 index 00000000..e0dbe0ac --- /dev/null +++ b/test/Ocelot.AcceptanceTests/StartupTests.cs @@ -0,0 +1,100 @@ +namespace Ocelot.AcceptanceTests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Net; + using System.Threading.Tasks; + using Configuration.Repository; + using Microsoft.AspNetCore.Http; + using Ocelot.Configuration.File; + using Responses; + using TestStack.BDDfy; + using Xunit; + + public class StartupTests : IDisposable + { + private readonly Steps _steps; + private readonly ServiceHandler _serviceHandler; + private string _downstreamPath; + + public StartupTests() + { + _serviceHandler = new ServiceHandler(); + _steps = new Steps(); + } + + [Fact] + public void should_not_try_and_write_to_disk_on_startup_when_not_using_admin_api() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 52179, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + var fakeRepo = new FakeFileConfigurationRepository(); + + this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:52179", "/", 200, "Hello from Laura")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningWithBlowingUpDiskRepo(fakeRepo)) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) + { + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => + { + _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; + + if (_downstreamPath != basePath) + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + }); + } + + public void Dispose() + { + _serviceHandler?.Dispose(); + _steps.Dispose(); + } + + class FakeFileConfigurationRepository : IFileConfigurationRepository + { + public Task> Get() + { + throw new NotImplementedException(); + } + + public Task Set(FileConfiguration fileConfiguration) + { + throw new NotImplementedException(); + } + } + } +} diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 62d1c816..7841083b 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -1,37 +1,35 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading; -using System.Threading.Tasks; -using CacheManager.Core; -using IdentityServer4.AccessTokenValidation; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Ocelot.DependencyInjection; -using Ocelot.Middleware; -using Shouldly; -using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; -using Ocelot.AcceptanceTests.Caching; -using System.IO.Compression; -using System.Text; -using static Ocelot.AcceptanceTests.HttpDelegatingHandlersTests; -using Ocelot.Requester; -using Ocelot.Middleware.Multiplexer; -using static Ocelot.Infrastructure.Wait; - -namespace Ocelot.AcceptanceTests +namespace Ocelot.AcceptanceTests { - using Microsoft.Net.Http.Headers; + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Threading; + using System.Threading.Tasks; + using IdentityServer4.AccessTokenValidation; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.TestHost; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Logging; + using Newtonsoft.Json; + using Ocelot.Configuration.File; + using Ocelot.DependencyInjection; + using Ocelot.Middleware; + using Shouldly; + using ConfigurationBuilder = Microsoft.Extensions.Configuration.ConfigurationBuilder; + using System.IO.Compression; + using System.Text; + using static Ocelot.AcceptanceTests.HttpDelegatingHandlersTests; + using Ocelot.Middleware.Multiplexer; + using static Ocelot.Infrastructure.Wait; + using Configuration.Repository; + using Ocelot.Configuration.Creator; + using CookieHeaderValue = System.Net.Http.Headers.CookieHeaderValue; using MediaTypeHeaderValue = System.Net.Http.Headers.MediaTypeHeaderValue; public class Steps : IDisposable @@ -41,7 +39,6 @@ namespace Ocelot.AcceptanceTests private HttpResponseMessage _response; private HttpContent _postContent; private BearerToken _token; - public HttpClient OcelotClient => _ocelotClient; public string RequestIdKey = "OcRequestId"; private readonly Random _random; private IWebHostBuilder _webHostBuilder; @@ -53,6 +50,18 @@ namespace Ocelot.AcceptanceTests _random = new Random(); } + public async Task ThenConfigShouldBe(FileConfiguration fileConfig) + { + var internalConfigCreator = _ocelotServer.Host.Services.GetService(); + + var internalConfigRepo = _ocelotServer.Host.Services.GetService(); + var internalConfig = internalConfigRepo.Get(); + + var config = await internalConfigCreator.Create(fileConfig); + + internalConfig.Data.RequestId.ShouldBe(config.Data.RequestId); + } + public async Task StartFakeOcelotWithWebSockets() { _ocelotBuilder = new WebHostBuilder(); @@ -68,9 +77,9 @@ namespace Ocelot.AcceptanceTests { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", false, false); config.AddEnvironmentVariables(); }) .ConfigureLogging((hostingContext, logging) => @@ -114,6 +123,34 @@ namespace Ocelot.AcceptanceTests File.WriteAllText(configurationPath, jsonConfiguration); } + public void GivenOcelotIsRunningReloadingConfig(bool shouldReload) + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", optional: false, reloadOnChange: shouldReload); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddOcelot(); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + } + /// /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. /// @@ -126,9 +163,9 @@ namespace Ocelot.AcceptanceTests { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", false, false); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -145,44 +182,6 @@ namespace Ocelot.AcceptanceTests _ocelotClient = _ocelotServer.CreateClient(); } - internal void GivenOcelotIsRunningUsingButterfly(string butterflyUrl) - { - _webHostBuilder = new WebHostBuilder(); - - _webHostBuilder - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(s => - { - s.AddOcelot() - .AddOpenTracing(option => - { - //this is the url that the butterfly collector server is running on... - option.CollectorUrl = butterflyUrl; - option.Service = "Ocelot"; - }); - }) - .Configure(app => - { - app.Use(async (context, next) => - { - await next.Invoke(); - }); - app.UseOcelot().Wait(); - }); - - _ocelotServer = new TestServer(_webHostBuilder); - - _ocelotClient = _ocelotServer.CreateClient(); - } - internal void GivenIWait(int wait) { Thread.Sleep(wait); @@ -197,9 +196,9 @@ namespace Ocelot.AcceptanceTests { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", optional: true, reloadOnChange: false); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -228,9 +227,9 @@ namespace Ocelot.AcceptanceTests { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", optional: true, reloadOnChange: false); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -261,9 +260,9 @@ namespace Ocelot.AcceptanceTests { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", optional: true, reloadOnChange: false); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -294,9 +293,9 @@ namespace Ocelot.AcceptanceTests { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", optional: true, reloadOnChange: false); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -326,9 +325,9 @@ namespace Ocelot.AcceptanceTests { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", optional: true, reloadOnChange: false); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -357,9 +356,9 @@ namespace Ocelot.AcceptanceTests { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", optional: true, reloadOnChange: false); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -396,9 +395,9 @@ namespace Ocelot.AcceptanceTests { config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", optional: true, reloadOnChange: false); config.AddEnvironmentVariables(); }) .ConfigureServices(s => @@ -423,146 +422,6 @@ namespace Ocelot.AcceptanceTests header.First().ShouldBe(value); } - public void ThenTheResponseBodyHeaderIs(string key, string value) - { - var header = _response.Content.Headers.GetValues(key); - header.First().ShouldBe(value); - } - - public void ThenTheTraceHeaderIsSet(string key) - { - var header = _response.Headers.GetValues(key); - header.First().ShouldNotBeNullOrEmpty(); - } - - public void GivenOcelotIsRunningUsingJsonSerializedCache() - { - _webHostBuilder = new WebHostBuilder(); - - _webHostBuilder - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(s => - { - s.AddOcelot() - .AddCacheManager((x) => - { - x.WithMicrosoftLogging(log => - { - log.AddConsole(LogLevel.Debug); - }) - .WithJsonSerializer() - .WithHandle(typeof(InMemoryJsonHandle<>)); - }); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - - _ocelotServer = new TestServer(_webHostBuilder); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - public void GivenOcelotIsRunningUsingConsulToStoreConfig() - { - _webHostBuilder = new WebHostBuilder(); - - _webHostBuilder - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(s => - { - s.AddOcelot().AddStoreOcelotConfigurationInConsul(); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - - _ocelotServer = new TestServer(_webHostBuilder); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - public void GivenOcelotIsRunningUsingConsulToStoreConfigAndJsonSerializedCache() - { - _webHostBuilder = new WebHostBuilder(); - - _webHostBuilder - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(s => - { - s.AddOcelot() - .AddCacheManager((x) => - { - x.WithMicrosoftLogging(log => - { - log.AddConsole(LogLevel.Debug); - }) - .WithJsonSerializer() - .WithHandle(typeof(InMemoryJsonHandle<>)); - }) - .AddStoreOcelotConfigurationInConsul(); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - - _ocelotServer = new TestServer(_webHostBuilder); - - _ocelotClient = _ocelotServer.CreateClient(); - } - - internal void ThenTheResponseShouldBe(FileConfiguration expecteds) - { - var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); - - response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey); - response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host); - response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port); - - for (var i = 0; i < response.ReRoutes.Count; i++) - { - for (var j = 0; j < response.ReRoutes[i].DownstreamHostAndPorts.Count; j++) - { - var result = response.ReRoutes[i].DownstreamHostAndPorts[j]; - var expected = expecteds.ReRoutes[i].DownstreamHostAndPorts[j]; - result.Host.ShouldBe(expected.Host); - result.Port.ShouldBe(expected.Port); - } - - response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].DownstreamPathTemplate); - response.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.ReRoutes[i].DownstreamScheme); - response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].UpstreamPathTemplate); - response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expecteds.ReRoutes[i].UpstreamHttpMethod); - } - } - /// /// This is annoying cos it should be in the constructor but we need to set up the file before calling startup so its a step. /// @@ -570,8 +429,8 @@ namespace Ocelot.AcceptanceTests { var builder = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) - .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile("ocelot.json") + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile("ocelot.json", false, false) .AddEnvironmentVariables(); var configuration = builder.Build(); @@ -585,15 +444,6 @@ namespace Ocelot.AcceptanceTests .UseConfiguration(configuration) .ConfigureServices(s => { - Action settings = (x) => - { - x.WithMicrosoftLogging(log => - { - log.AddConsole(LogLevel.Debug); - }) - .WithDictionaryHandle(); - }; - s.AddOcelot(configuration); }) .ConfigureLogging(l => @@ -683,26 +533,6 @@ namespace Ocelot.AcceptanceTests } } - public void GivenIHaveAnOcelotToken(string adminPath) - { - var tokenUrl = $"{adminPath}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", "admin"), - new KeyValuePair("client_secret", "secret"), - new KeyValuePair("scope", "admin"), - new KeyValuePair("username", "admin"), - new KeyValuePair("password", "admin"), - new KeyValuePair("grant_type", "password") - }; - var content = new FormUrlEncodedContent(formData); - - var response = _ocelotClient.PostAsync(tokenUrl, content).Result; - var responseContent = response.Content.ReadAsStringAsync().Result; - response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); - } - public void VerifyIdentiryServerStarted(string url) { using (var httpClient = new HttpClient()) @@ -718,24 +548,6 @@ namespace Ocelot.AcceptanceTests _response = _ocelotClient.GetAsync(url).Result; } - public void WhenIGetUrlOnTheApiGatewayWaitingForTheResponseToBeOk(string url) - { - var result = WaitFor(2000).Until(() => { - try - { - _response = _ocelotClient.GetAsync(url).Result; - _response.EnsureSuccessStatusCode(); - return true; - } - catch(Exception) - { - return false; - } - }); - - result.ShouldBeTrue(); - } - public void WhenIGetUrlOnTheApiGateway(string url, string cookie, string value) { var request = _ocelotServer.CreateRequest(url); @@ -746,7 +558,7 @@ namespace Ocelot.AcceptanceTests public void GivenIAddAHeader(string key, string value) { - _ocelotClient.DefaultRequestHeaders.Add(key, value); + _ocelotClient.DefaultRequestHeaders.TryAddWithoutValidation(key, value); } public void WhenIGetUrlOnTheApiGatewayMultipleTimes(string url, int times) @@ -823,6 +635,11 @@ namespace Ocelot.AcceptanceTests _postContent = new StringContent(postcontent); } + public void GivenThePostHasContentType(string postcontent) + { + _postContent.Headers.ContentType = new MediaTypeHeaderValue(postcontent); + } + public void GivenThePostHasGzipContent(object input) { var json = JsonConvert.SerializeObject(input); @@ -873,11 +690,6 @@ namespace Ocelot.AcceptanceTests _response.Headers.GetValues(RequestIdKey).First().ShouldBe(expected); } - public void ThenTheContentLengthIs(int expected) - { - _response.Content.Headers.ContentLength.ShouldBe(expected); - } - public void WhenIMakeLotsOfDifferentRequestsToTheApiGateway() { int numberOfRequests = 100; @@ -923,5 +735,35 @@ namespace Ocelot.AcceptanceTests var content = await response.Content.ReadAsStringAsync(); content.ShouldBe(expectedBody); } + + public void GivenOcelotIsRunningWithBlowingUpDiskRepo(IFileConfigurationRepository fake) + { + _webHostBuilder = new WebHostBuilder(); + + _webHostBuilder + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", false, false); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(s => + { + s.AddSingleton(fake); + s.AddOcelot(); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + + _ocelotServer = new TestServer(_webHostBuilder); + + _ocelotClient = _ocelotServer.CreateClient(); + + } } } diff --git a/test/Ocelot.AcceptanceTests/StickySessionsTests.cs b/test/Ocelot.AcceptanceTests/StickySessionsTests.cs index 254eb16a..68bb7c3f 100644 --- a/test/Ocelot.AcceptanceTests/StickySessionsTests.cs +++ b/test/Ocelot.AcceptanceTests/StickySessionsTests.cs @@ -1,34 +1,31 @@ -using System; -using System.Collections.Generic; -using System.IO; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - namespace Ocelot.AcceptanceTests { + using System; + using System.Collections.Generic; + using Microsoft.AspNetCore.Http; + using Ocelot.Configuration.File; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + public class StickySessionsTests : IDisposable { - private IWebHost _builderOne; - private IWebHost _builderTwo; private readonly Steps _steps; private int _counterOne; private int _counterTwo; - private static readonly object _syncLock = new object(); + private static readonly object SyncLock = new object(); + private readonly ServiceHandler _serviceHandler; public StickySessionsTests() { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); } [Fact] public void should_use_same_downstream_host() { - var downstreamPortOne = 51881; + var downstreamPortOne = 51375; var downstreamPortTwo = 51892; var downstreamServiceOneUrl = $"http://localhost:{downstreamPortOne}"; var downstreamServiceTwoUrl = $"http://localhost:{downstreamPortTwo}"; @@ -244,77 +241,52 @@ namespace Ocelot.AcceptanceTests private void GivenProductServiceOneIsRunning(string url, int statusCode) { - _builderOne = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => + _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => + { + try { - app.Run(async context => + var response = string.Empty; + lock (SyncLock) { - try - { - var response = string.Empty; - lock (_syncLock) - { - _counterOne++; - response = _counterOne.ToString(); - } - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(response); - } - catch (Exception exception) - { - await context.Response.WriteAsync(exception.StackTrace); - } - }); - }) - .Build(); - - _builderOne.Start(); + _counterOne++; + response = _counterOne.ToString(); + } + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(response); + } + catch (Exception exception) + { + await context.Response.WriteAsync(exception.StackTrace); + } + }); } private void GivenProductServiceTwoIsRunning(string url, int statusCode) { - _builderTwo = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => + _serviceHandler.GivenThereIsAServiceRunningOn(url, async context => + { + try { - app.Run(async context => + var response = string.Empty; + lock (SyncLock) { - try - { - var response = string.Empty; - lock (_syncLock) - { - _counterTwo++; - response = _counterTwo.ToString(); - } - - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(response); - } - catch (System.Exception exception) - { - await context.Response.WriteAsync(exception.StackTrace); - } - }); - }) - .Build(); + _counterTwo++; + response = _counterTwo.ToString(); + } - _builderTwo.Start(); + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(response); + } + catch (Exception exception) + { + await context.Response.WriteAsync(exception.StackTrace); + } + }); } public void Dispose() { - _builderOne?.Dispose(); - _builderTwo?.Dispose(); + _serviceHandler?.Dispose(); _steps.Dispose(); } } diff --git a/test/Ocelot.AcceptanceTests/TwoDownstreamServicesTests.cs b/test/Ocelot.AcceptanceTests/TwoDownstreamServicesTests.cs deleted file mode 100644 index 203f4e7c..00000000 --- a/test/Ocelot.AcceptanceTests/TwoDownstreamServicesTests.cs +++ /dev/null @@ -1,191 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using Consul; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.AcceptanceTests -{ - public class TwoDownstreamServicesTests : IDisposable - { - private IWebHost _builderOne; - private IWebHost _builderTwo; - private IWebHost _fakeConsulBuilder; - private readonly Steps _steps; - private readonly List _serviceEntries; - private string _downstreamPathOne; - private string _downstreamPathTwo; - - public TwoDownstreamServicesTests() - { - _steps = new Steps(); - _serviceEntries = new List(); - } - - [Fact] - public void should_fix_issue_194() - { - var consulPort = 8503; - var downstreamServiceOneUrl = "http://localhost:8362"; - var downstreamServiceTwoUrl = "http://localhost:8330"; - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/user/{user}", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 8362, - } - }, - UpstreamPathTemplate = "/api/user/{user}", - UpstreamHttpMethod = new List { "Get" }, - }, - new FileReRoute - { - DownstreamPathTemplate = "/api/product/{product}", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 8330, - } - }, - UpstreamPathTemplate = "/api/product/{product}", - UpstreamHttpMethod = new List { "Get" }, - } - }, - GlobalConfiguration = new FileGlobalConfiguration() - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider() - { - Host = "localhost", - Port = consulPort - } - } - }; - - this.Given(x => x.GivenProductServiceOneIsRunning(downstreamServiceOneUrl, "/api/user/info", 200, "user")) - .And(x => x.GivenProductServiceTwoIsRunning(downstreamServiceTwoUrl, "/api/product/info", 200, "product")) - .And(x => x.GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/user/info?id=1")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("user")) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/api/product/info?id=1")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe("product")) - .BDDfy(); - } - - private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url) - { - _fakeConsulBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - if(context.Request.Path.Value == "/v1/health/service/product") - { - await context.Response.WriteJsonAsync(_serviceEntries); - } - }); - }) - .Build(); - - _fakeConsulBuilder.Start(); - } - - private void GivenProductServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) - { - _builderOne = new WebHostBuilder() - .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; - - 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(); - - _builderOne.Start(); - } - - private void GivenProductServiceTwoIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) - { - _builderTwo = new WebHostBuilder() - .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; - - 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(); - - _builderTwo.Start(); - } - - public void Dispose() - { - _builderOne?.Dispose(); - _builderTwo?.Dispose(); - _steps.Dispose(); - } - } -} diff --git a/test/Ocelot.AcceptanceTests/UpstreamHostTests.cs b/test/Ocelot.AcceptanceTests/UpstreamHostTests.cs index a6a495a1..aa4affde 100644 --- a/test/Ocelot.AcceptanceTests/UpstreamHostTests.cs +++ b/test/Ocelot.AcceptanceTests/UpstreamHostTests.cs @@ -1,31 +1,30 @@ -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; - 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 UpstreamHostTests : IDisposable { - private IWebHost _builder; private readonly Steps _steps; private string _downstreamPath; + private readonly ServiceHandler _serviceHandler; public UpstreamHostTests() { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); } [Fact] public void should_return_response_200_with_simple_url_and_hosts_match() { + int port = 64905; + var configuration = new FileConfiguration { ReRoutes = new List @@ -39,7 +38,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51875, + Port = port, } }, UpstreamPathTemplate = "/", @@ -49,7 +48,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51875", "/", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -61,6 +60,8 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_return_response_200_with_simple_url_and_hosts_match_multiple_re_routes() { + int port = 64904; + var configuration = new FileConfiguration { ReRoutes = new List @@ -74,7 +75,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51875, + Port = port, } }, UpstreamPathTemplate = "/", @@ -100,7 +101,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51875", "/", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -112,6 +113,8 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_return_response_200_with_simple_url_and_hosts_match_multiple_re_routes_reversed() { + int port = 64903; + var configuration = new FileConfiguration { ReRoutes = new List @@ -141,7 +144,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51875, + Port = port, } }, UpstreamPathTemplate = "/", @@ -151,7 +154,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51875", "/", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -163,6 +166,8 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_return_response_200_with_simple_url_and_hosts_match_multiple_re_routes_reversed_with_no_host_first() { + int port = 64902; + var configuration = new FileConfiguration { ReRoutes = new List @@ -191,7 +196,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51875, + Port = port, } }, UpstreamPathTemplate = "/", @@ -201,7 +206,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51875", "/", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -213,6 +218,8 @@ namespace Ocelot.AcceptanceTests [Fact] public void should_return_response_404_with_simple_url_and_hosts_dont_match() { + int port = 64901; + var configuration = new FileConfiguration { ReRoutes = new List @@ -226,7 +233,7 @@ namespace Ocelot.AcceptanceTests new FileHostAndPort { Host = "localhost", - Port = 51875, + Port = port, } }, UpstreamPathTemplate = "/", @@ -236,7 +243,7 @@ namespace Ocelot.AcceptanceTests } }; - this.Given(x => x.GivenThereIsAServiceRunningOn("http://localhost:51875", "/", 200, "Hello from Laura")) + this.Given(x => x.GivenThereIsAServiceRunningOn($"http://localhost:{port}", "/", 200, "Hello from Laura")) .And(x => _steps.GivenThereIsAConfiguration(configuration)) .And(x => _steps.GivenOcelotIsRunning()) .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) @@ -246,43 +253,26 @@ namespace Ocelot.AcceptanceTests private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) { - _builder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, async context => + { + _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; + + if (_downstreamPath != basePath) { - app.UsePathBase(basePath); - app.Run(async context => - { - _downstreamPath = !string.IsNullOrEmpty(context.Request.PathBase.Value) ? context.Request.PathBase.Value : context.Request.Path.Value; - - if(_downstreamPath != basePath) - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync("downstream path didnt match base path"); - } - else - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - } - }); - }) - .Build(); - - _builder.Start(); - } - - internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPath) - { - _downstreamPath.ShouldBe(expectedDownstreamPath); + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync("downstream path didnt match base path"); + } + else + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + } + }); } public void Dispose() { - _builder?.Dispose(); + _serviceHandler?.Dispose(); _steps.Dispose(); } } diff --git a/test/Ocelot.AcceptanceTests/WebSocketTests.cs b/test/Ocelot.AcceptanceTests/WebSocketTests.cs index 667fb5fd..77661613 100644 --- a/test/Ocelot.AcceptanceTests/WebSocketTests.cs +++ b/test/Ocelot.AcceptanceTests/WebSocketTests.cs @@ -1,39 +1,29 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.WebSockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Consul; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - namespace Ocelot.AcceptanceTests { + using System; + using System.Collections.Generic; + using System.Net.WebSockets; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using Ocelot.Configuration.File; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + public class WebSocketTests : IDisposable { - private IWebHost _firstDownstreamHost; - private IWebHost _secondDownstreamHost; private readonly List _secondRecieved; private readonly List _firstRecieved; - private readonly List _serviceEntries; private readonly Steps _steps; - private IWebHost _fakeConsulBuilder; + private readonly ServiceHandler _serviceHandler; public WebSocketTests() { + _serviceHandler = new ServiceHandler(); _steps = new Steps(); _firstRecieved = new List(); _secondRecieved = new List(); - _serviceEntries = new List(); } [Fact] @@ -115,77 +105,6 @@ namespace Ocelot.AcceptanceTests .BDDfy(); } - [Fact] - public void should_proxy_websocket_input_to_downstream_service_and_use_service_discovery_and_load_balancer() - { - var downstreamPort = 5007; - var downstreamHost = "localhost"; - - var secondDownstreamPort = 5008; - var secondDownstreamHost = "localhost"; - - var serviceName = "websockets"; - var consulPort = 8509; - var fakeConsulServiceDiscoveryUrl = $"http://localhost:{consulPort}"; - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = downstreamHost, - Port = downstreamPort, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - var serviceEntryTwo = new ServiceEntry() - { - Service = new AgentService() - { - Service = serviceName, - Address = secondDownstreamHost, - Port = secondDownstreamPort, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - - var config = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - UpstreamPathTemplate = "/", - DownstreamPathTemplate = "/ws", - DownstreamScheme = "ws", - LoadBalancerOptions = new FileLoadBalancerOptions { Type = "RoundRobin" }, - ServiceName = serviceName, - UseServiceDiscovery = true - } - }, - GlobalConfiguration = new FileGlobalConfiguration - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider - { - Host = "localhost", - Port = consulPort, - Type = "consul" - } - } - }; - - this.Given(_ => _steps.GivenThereIsAConfiguration(config)) - .And(_ => _steps.StartFakeOcelotWithWebSockets()) - .And(_ => GivenThereIsAFakeConsulServiceDiscoveryProvider(fakeConsulServiceDiscoveryUrl, serviceName)) - .And(_ => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo)) - .And(_ => StartFakeDownstreamService($"http://{downstreamHost}:{downstreamPort}", "/ws")) - .And(_ => StartSecondFakeDownstreamService($"http://{secondDownstreamHost}:{secondDownstreamPort}", "/ws")) - .When(_ => WhenIStartTheClients()) - .Then(_ => ThenBothDownstreamServicesAreCalled()) - .BDDfy(); - } - private void ThenBothDownstreamServicesAreCalled() { _firstRecieved.Count.ShouldBe(10); @@ -201,37 +120,6 @@ namespace Ocelot.AcceptanceTests }); } - private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries) - { - foreach (var serviceEntry in serviceEntries) - { - _serviceEntries.Add(serviceEntry); - } - } - - private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName) - { - _fakeConsulBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - if (context.Request.Path.Value == $"/v1/health/service/{serviceName}") - { - await context.Response.WriteJsonAsync(_serviceEntries); - } - }); - }) - .Build(); - - _fakeConsulBuilder.Start(); - } - private async Task WhenIStartTheClients() { var firstClient = StartClient("ws://localhost:5000/"); @@ -333,94 +221,48 @@ namespace Ocelot.AcceptanceTests private async Task StartFakeDownstreamService(string url, string path) { - _firstDownstreamHost = new WebHostBuilder() - .ConfigureServices(s => { }).UseKestrel() - .UseUrls(url) - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => + await _serviceHandler.StartFakeDownstreamService(url, path, async(context, next) => + { + if (context.Request.Path == path) { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddEnvironmentVariables(); - }) - .ConfigureLogging((hostingContext, logging) => - { - logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - logging.AddConsole(); - }) - .Configure(app => - { - app.UseWebSockets(); - app.Use(async (context, next) => + if (context.WebSockets.IsWebSocketRequest) { - if (context.Request.Path == path) - { - if (context.WebSockets.IsWebSocketRequest) - { - WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync(); - await Echo(webSocket); - } - else - { - context.Response.StatusCode = 400; - } - } - else - { - await next(); - } - }); - }) - .UseIISIntegration().Build(); - await _firstDownstreamHost.StartAsync(); + var webSocket = await context.WebSockets.AcceptWebSocketAsync(); + await Echo(webSocket); + } + else + { + context.Response.StatusCode = 400; + } + } + else + { + await next(); + } + }); } private async Task StartSecondFakeDownstreamService(string url, string path) { - _secondDownstreamHost = new WebHostBuilder() - .ConfigureServices(s => { }).UseKestrel() - .UseUrls(url) - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => + await _serviceHandler.StartFakeDownstreamService(url, path, async (context, next) => + { + if (context.Request.Path == path) { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddEnvironmentVariables(); - }) - .ConfigureLogging((hostingContext, logging) => - { - logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - logging.AddConsole(); - }) - .Configure(app => - { - app.UseWebSockets(); - app.Use(async (context, next) => + if (context.WebSockets.IsWebSocketRequest) { - if (context.Request.Path == path) - { - if (context.WebSockets.IsWebSocketRequest) - { - WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync(); - await Message(webSocket); - } - else - { - context.Response.StatusCode = 400; - } - } - else - { - await next(); - } - }); - }) - .UseIISIntegration().Build(); - await _secondDownstreamHost.StartAsync(); + WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync(); + await Message(webSocket); + } + else + { + context.Response.StatusCode = 400; + } + } + else + { + await next(); + } + }); } private async Task Echo(WebSocket webSocket) @@ -473,10 +315,8 @@ namespace Ocelot.AcceptanceTests public void Dispose() { + _serviceHandler?.Dispose(); _steps.Dispose(); - _firstDownstreamHost?.Dispose(); - _secondDownstreamHost?.Dispose(); - _fakeConsulBuilder?.Dispose(); } } } diff --git a/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs b/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs index 053ac7ea..8db90a24 100644 --- a/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs +++ b/test/Ocelot.Benchmarks/AllTheThingsBenchmarks.cs @@ -1,157 +1,157 @@ -using System; -using System.Collections.Generic; -using System.IO; -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Columns; -using BenchmarkDotNet.Configs; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Middleware; -using Ocelot.DependencyInjection; -using System.Net.Http; -using System.Threading.Tasks; -using BenchmarkDotNet.Attributes.Jobs; -using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Validators; - -namespace Ocelot.Benchmarks -{ - [Config(typeof(AllTheThingsBenchmarks))] - public class AllTheThingsBenchmarks : ManualConfig - { - private IWebHost _service; - private IWebHost _ocelot; - private HttpClient _httpClient; - - public AllTheThingsBenchmarks() - { - Add(StatisticColumn.AllStatistics); - Add(MemoryDiagnoser.Default); - Add(BaselineValidator.FailOnError); - } - - [GlobalSetup] - public void SetUp() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51879, - } - }, - DownstreamScheme = "http", - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 201, string.Empty); - GivenThereIsAConfiguration(configuration); - GivenOcelotIsRunning("http://localhost:5000"); - - _httpClient = new HttpClient(); - } - - [Benchmark(Baseline = true)] - public async Task Baseline() - { - var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:5000/"); - var response = await _httpClient.SendAsync(request); - response.EnsureSuccessStatusCode(); - } - -/* * Summary* - BenchmarkDotNet = v0.10.13, OS = macOS 10.12.6 (16G1212) [Darwin 16.7.0] - Intel Core i5-4278U CPU 2.60GHz(Haswell), 1 CPU, 4 logical cores and 2 physical cores - .NET Core SDK = 2.1.4 - - [Host] : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT - DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT - Method | Mean | Error | StdDev | StdErr | Min | Q1 | Median | Q3 | Max | Op/s | Scaled | Gen 0 | Gen 1 | Allocated | - --------- |---------:|----------:|----------:|----------:|---------:|---------:|---------:|---------:|---------:|------:|-------:|--------:|-------:|----------:| - Baseline | 2.102 ms | 0.0292 ms | 0.0273 ms | 0.0070 ms | 2.063 ms | 2.080 ms | 2.093 ms | 2.122 ms | 2.152 ms | 475.8 | 1.00 | 31.2500 | 3.9063 | 1.63 KB |*/ - - private void GivenOcelotIsRunning(string url) - { - _ocelot = new WebHostBuilder() - .UseKestrel() - .UseUrls(url) - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config - .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) - .AddJsonFile("appsettings.json", true, true) - .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("ocelot.json") - .AddEnvironmentVariables(); - }) - .ConfigureServices(s => { - s.AddOcelot(); - }) - .ConfigureLogging((hostingContext, logging) => - { - logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); - }) - .UseIISIntegration() - .Configure(app => - { - app.UseOcelot().Wait(); - }) - .Build(); - - _ocelot.Start(); - } - - public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) - { - var configurationPath = Path.Combine(AppContext.BaseDirectory, "ocelot.json"); - - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - } - - private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) - { - _service = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => - { - app.UsePathBase(basePath); - app.Run(async context => - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - }) - .Build(); - - _service.Start(); - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Ocelot.DownstreamRouteFinder.UrlMatcher; +using Ocelot.Middleware; +using Ocelot.DependencyInjection; +using System.Net.Http; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes.Jobs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Validators; + +namespace Ocelot.Benchmarks +{ + [Config(typeof(AllTheThingsBenchmarks))] + public class AllTheThingsBenchmarks : ManualConfig + { + private IWebHost _service; + private IWebHost _ocelot; + private HttpClient _httpClient; + + public AllTheThingsBenchmarks() + { + Add(StatisticColumn.AllStatistics); + Add(MemoryDiagnoser.Default); + Add(BaselineValidator.FailOnError); + } + + [GlobalSetup] + public void SetUp() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + DownstreamScheme = "http", + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + GivenThereIsAServiceRunningOn("http://localhost:51879", "/", 201, string.Empty); + GivenThereIsAConfiguration(configuration); + GivenOcelotIsRunning("http://localhost:5000"); + + _httpClient = new HttpClient(); + } + + [Benchmark(Baseline = true)] + public async Task Baseline() + { + var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost:5000/"); + var response = await _httpClient.SendAsync(request); + response.EnsureSuccessStatusCode(); + } + +/* * Summary* + BenchmarkDotNet = v0.10.13, OS = macOS 10.12.6 (16G1212) [Darwin 16.7.0] + Intel Core i5-4278U CPU 2.60GHz(Haswell), 1 CPU, 4 logical cores and 2 physical cores + .NET Core SDK = 2.1.4 + + [Host] : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT + DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.0.0, CoreFX 4.6.26212.01), 64bit RyuJIT + Method | Mean | Error | StdDev | StdErr | Min | Q1 | Median | Q3 | Max | Op/s | Scaled | Gen 0 | Gen 1 | Allocated | + --------- |---------:|----------:|----------:|----------:|---------:|---------:|---------:|---------:|---------:|------:|-------:|--------:|-------:|----------:| + Baseline | 2.102 ms | 0.0292 ms | 0.0273 ms | 0.0070 ms | 2.063 ms | 2.080 ms | 2.093 ms | 2.122 ms | 2.152 ms | 475.8 | 1.00 | 31.2500 | 3.9063 | 1.63 KB |*/ + + private void GivenOcelotIsRunning(string url) + { + _ocelot = new WebHostBuilder() + .UseKestrel() + .UseUrls(url) + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config + .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) + .AddJsonFile("appsettings.json", true, true) + .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) + .AddJsonFile("ocelot.json", false, false) + .AddEnvironmentVariables(); + }) + .ConfigureServices(s => { + s.AddOcelot(); + }) + .ConfigureLogging((hostingContext, logging) => + { + logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); + }) + .UseIISIntegration() + .Configure(app => + { + app.UseOcelot().Wait(); + }) + .Build(); + + _ocelot.Start(); + } + + public void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) + { + var configurationPath = Path.Combine(AppContext.BaseDirectory, "ocelot.json"); + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + } + + private void GivenThereIsAServiceRunningOn(string baseUrl, string basePath, int statusCode, string responseBody) + { + _service = new WebHostBuilder() + .UseUrls(baseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.UsePathBase(basePath); + app.Run(async context => + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + }) + .Build(); + + _service.Start(); + } + } +} diff --git a/test/Ocelot.IntegrationTests/AdministrationTests.cs b/test/Ocelot.IntegrationTests/AdministrationTests.cs deleted file mode 100644 index 44a89929..00000000 --- a/test/Ocelot.IntegrationTests/AdministrationTests.cs +++ /dev/null @@ -1,852 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Security.Claims; -using CacheManager.Core; -using IdentityServer4.AccessTokenValidation; -using IdentityServer4.Models; -using IdentityServer4.Test; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -using Ocelot.Cache; -using Ocelot.Configuration.File; -using Ocelot.DependencyInjection; -using Ocelot.Middleware; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -[assembly: CollectionBehavior(DisableTestParallelization = true)] -namespace Ocelot.IntegrationTests -{ - public class AdministrationTests : IDisposable - { - private HttpClient _httpClient; - private readonly HttpClient _httpClientTwo; - private HttpResponseMessage _response; - private IWebHost _builder; - private IWebHostBuilder _webHostBuilder; - private string _ocelotBaseUrl; - private BearerToken _token; - private IWebHostBuilder _webHostBuilderTwo; - private IWebHost _builderTwo; - private IWebHost _identityServerBuilder; - private IWebHost _fooServiceBuilder; - private IWebHost _barServiceBuilder; - - public AdministrationTests() - { - _httpClient = new HttpClient(); - _httpClientTwo = new HttpClient(); - _ocelotBaseUrl = "http://localhost:5000"; - _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); - } - - [Fact] - public void should_return_response_401_with_call_re_routes_controller() - { - var configuration = new FileConfiguration(); - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenOcelotIsRunning()) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.Unauthorized)) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_call_re_routes_controller() - { - var configuration = new FileConfiguration(); - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenOcelotIsRunning()) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_call_re_routes_controller_using_base_url_added_in_file_config() - { - _httpClient = new HttpClient(); - _ocelotBaseUrl = "http://localhost:5011"; - _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); - - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - BaseUrl = _ocelotBaseUrl - } - }; - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenOcelotIsRunningWithNoWebHostBuilder(_ocelotBaseUrl)) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - [Fact] - public void should_be_able_to_use_token_from_ocelot_a_on_ocelot_b() - { - var configuration = new FileConfiguration(); - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenIdentityServerSigningEnvironmentalVariablesAreSet()) - .And(x => GivenOcelotIsRunning()) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenAnotherOcelotIsRunning("http://localhost:5007")) - .When(x => WhenIGetUrlOnTheSecondOcelot("/administration/configuration")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - [Fact] - public void should_return_file_configuration() - { - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - RequestIdKey = "RequestId", - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider - { - Host = "127.0.0.1", - } - }, - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/", - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 10, - Region = "Geoff" - } - }, - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/test", - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 10, - Region = "Dave" - } - } - } - }; - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenOcelotIsRunning()) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => ThenTheResponseShouldBe(configuration)) - .BDDfy(); - } - - [Fact] - public void should_get_file_configuration_edit_and_post_updated_version() - { - var initialConfiguration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - }, - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/" - }, - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/test" - } - } - }; - - var updatedConfiguration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - }, - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - } - }, - DownstreamScheme = "http", - DownstreamPathTemplate = "/geoffrey", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/" - }, - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "123.123.123", - Port = 443, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/blooper/{productId}", - UpstreamHttpMethod = new List { "post" }, - UpstreamPathTemplate = "/test" - } - } - }; - - this.Given(x => GivenThereIsAConfiguration(initialConfiguration)) - .And(x => GivenOcelotIsRunning()) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .When(x => WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration)) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => ThenTheResponseShouldBe(updatedConfiguration)) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .And(x => ThenTheResponseShouldBe(updatedConfiguration)) - .BDDfy(); - } - - [Fact] - public void should_get_file_configuration_edit_and_post_updated_version_redirecting_reroute() - { - var fooPort = 47689; - var barPort = 47690; - - var initialConfiguration = new FileConfiguration - { - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = fooPort, - } - }, - DownstreamScheme = "http", - DownstreamPathTemplate = "/foo", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/foo" - } - } - }; - - var updatedConfiguration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - }, - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = barPort, - } - }, - DownstreamScheme = "http", - DownstreamPathTemplate = "/bar", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/foo" - } - } - }; - - this.Given(x => GivenThereIsAConfiguration(initialConfiguration)) - .And(x => GivenThereIsAFooServiceRunningOn($"http://localhost:{fooPort}")) - .And(x => GivenThereIsABarServiceRunningOn($"http://localhost:{barPort}")) - .And(x => GivenOcelotIsRunning()) - .And(x => WhenIGetUrlOnTheApiGateway("/foo")) - .Then(x => ThenTheResponseBodyShouldBe("foo")) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration)) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => ThenTheResponseShouldBe(updatedConfiguration)) - .And(x => WhenIGetUrlOnTheApiGateway("/foo")) - .Then(x => ThenTheResponseBodyShouldBe("bar")) - .When(x => WhenIPostOnTheApiGateway("/administration/configuration", initialConfiguration)) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => ThenTheResponseShouldBe(initialConfiguration)) - .And(x => WhenIGetUrlOnTheApiGateway("/foo")) - .Then(x => ThenTheResponseBodyShouldBe("foo")) - .BDDfy(); - } - - [Fact] - public void should_clear_region() - { - var initialConfiguration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - }, - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/", - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 10 - } - }, - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/test", - FileCacheOptions = new FileCacheOptions - { - TtlSeconds = 10 - } - } - } - }; - - var regionToClear = "gettest"; - - this.Given(x => GivenThereIsAConfiguration(initialConfiguration)) - .And(x => GivenOcelotIsRunning()) - .And(x => GivenIHaveAnOcelotToken("/administration")) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIDeleteOnTheApiGateway($"/administration/outputcache/{regionToClear}")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.NoContent)) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_call_re_routes_controller_when_using_own_identity_server_to_secure_admin_area() - { - var configuration = new FileConfiguration(); - - var identityServerRootUrl = "http://localhost:5123"; - - Action options = o => { - o.Authority = identityServerRootUrl; - o.ApiName = "api"; - o.RequireHttpsMetadata = false; - o.SupportedTokens = SupportedTokens.Both; - o.ApiSecret = "secret"; - }; - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenThereIsAnIdentityServerOn(identityServerRootUrl, "api")) - .And(x => GivenOcelotIsRunningWithIdentityServerSettings(options)) - .And(x => GivenIHaveAToken(identityServerRootUrl)) - .And(x => GivenIHaveAddedATokenToMyRequest()) - .When(x => WhenIGetUrlOnTheApiGateway("/administration/configuration")) - .Then(x => ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .BDDfy(); - } - - private void GivenIHaveAToken(string url) - { - var formData = new List> - { - new KeyValuePair("client_id", "api"), - new KeyValuePair("client_secret", "secret"), - new KeyValuePair("scope", "api"), - new KeyValuePair("username", "test"), - new KeyValuePair("password", "test"), - new KeyValuePair("grant_type", "password") - }; - var content = new FormUrlEncodedContent(formData); - - using (var httpClient = new HttpClient()) - { - var response = httpClient.PostAsync($"{url}/connect/token", content).Result; - var responseContent = response.Content.ReadAsStringAsync().Result; - response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); - } - } - - private void GivenThereIsAnIdentityServerOn(string url, string apiName) - { - _identityServerBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureServices(services => - { - services.AddLogging(); - services.AddIdentityServer() - .AddDeveloperSigningCredential() - .AddInMemoryApiResources(new List - { - new ApiResource - { - Name = apiName, - Description = apiName, - Enabled = true, - DisplayName = apiName, - Scopes = new List() - { - new Scope(apiName) - } - } - }) - .AddInMemoryClients(new List - { - new Client - { - ClientId = apiName, - AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, - ClientSecrets = new List {new Secret("secret".Sha256())}, - AllowedScopes = new List { apiName }, - AccessTokenType = AccessTokenType.Jwt, - Enabled = true - } - }) - .AddTestUsers(new List - { - new TestUser - { - Username = "test", - Password = "test", - SubjectId = "1231231" - } - }); - }) - .Configure(app => - { - app.UseIdentityServer(); - }) - .Build(); - - _identityServerBuilder.Start(); - - using (var httpClient = new HttpClient()) - { - var response = httpClient.GetAsync($"{url}/.well-known/openid-configuration").Result; - response.EnsureSuccessStatusCode(); - } - } - - private void GivenAnotherOcelotIsRunning(string baseUrl) - { - _httpClientTwo.BaseAddress = new Uri(baseUrl); - - _webHostBuilderTwo = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(x => - { - Action settings = (s) => - { - s.WithMicrosoftLogging(log => - { - log.AddConsole(LogLevel.Debug); - }) - .WithDictionaryHandle(); - }; - - x.AddOcelot() - .AddCacheManager(settings) - .AddAdministration("/administration", "secret"); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - - _builderTwo = _webHostBuilderTwo.Build(); - - _builderTwo.Start(); - } - - private void GivenIdentityServerSigningEnvironmentalVariablesAreSet() - { - Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", "idsrv3test.pfx"); - Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", "idsrv3test"); - } - - private void WhenIGetUrlOnTheSecondOcelot(string url) - { - _httpClientTwo.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - _response = _httpClientTwo.GetAsync(url).Result; - } - - private void WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration) - { - var json = JsonConvert.SerializeObject(updatedConfiguration); - var content = new StringContent(json); - content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - _response = _httpClient.PostAsync(url, content).Result; - } - - private void ThenTheResponseShouldBe(List expected) - { - var content = _response.Content.ReadAsStringAsync().Result; - var result = JsonConvert.DeserializeObject(content); - result.Value.ShouldBe(expected); - } - - private void ThenTheResponseBodyShouldBe(string expected) - { - var content = _response.Content.ReadAsStringAsync().Result; - content.ShouldBe(expected); - } - - private void ThenTheResponseShouldBe(FileConfiguration expecteds) - { - var response = JsonConvert.DeserializeObject(_response.Content.ReadAsStringAsync().Result); - - response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey); - response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host); - response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port); - - for (var i = 0; i < response.ReRoutes.Count; i++) - { - for (var j = 0; j < response.ReRoutes[i].DownstreamHostAndPorts.Count; j++) - { - var result = response.ReRoutes[i].DownstreamHostAndPorts[j]; - var expected = expecteds.ReRoutes[i].DownstreamHostAndPorts[j]; - result.Host.ShouldBe(expected.Host); - result.Port.ShouldBe(expected.Port); - } - - response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].DownstreamPathTemplate); - response.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.ReRoutes[i].DownstreamScheme); - response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].UpstreamPathTemplate); - response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expecteds.ReRoutes[i].UpstreamHttpMethod); - } - } - - private void GivenIHaveAddedATokenToMyRequest() - { - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - } - - private void GivenIHaveAnOcelotToken(string adminPath) - { - var tokenUrl = $"{adminPath}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", "admin"), - new KeyValuePair("client_secret", "secret"), - new KeyValuePair("scope", "admin"), - new KeyValuePair("grant_type", "client_credentials") - }; - var content = new FormUrlEncodedContent(formData); - - var response = _httpClient.PostAsync(tokenUrl, content).Result; - var responseContent = response.Content.ReadAsStringAsync().Result; - response.EnsureSuccessStatusCode(); - _token = JsonConvert.DeserializeObject(responseContent); - var configPath = $"{adminPath}/.well-known/openid-configuration"; - response = _httpClient.GetAsync(configPath).Result; - response.EnsureSuccessStatusCode(); - } - - private void GivenOcelotIsRunningWithIdentityServerSettings(Action configOptions) - { - _webHostBuilder = new WebHostBuilder() - .UseUrls(_ocelotBaseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(x => { - x.AddSingleton(_webHostBuilder); - x.AddOcelot() - .AddCacheManager(c => - { - c.WithDictionaryHandle(); - }) - .AddAdministration("/administration", configOptions); - }) - .Configure(app => { - app.UseOcelot().Wait(); - }); - - _builder = _webHostBuilder.Build(); - - _builder.Start(); - } - - private void GivenOcelotIsRunning() - { - _webHostBuilder = new WebHostBuilder() - .UseUrls(_ocelotBaseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(x => - { - Action settings = (s) => - { - s.WithMicrosoftLogging(log => - { - log.AddConsole(LogLevel.Debug); - }) - .WithDictionaryHandle(); - }; - - x.AddOcelot() - .AddCacheManager(settings) - .AddAdministration("/administration", "secret"); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - - _builder = _webHostBuilder.Build(); - - _builder.Start(); - } - - private void GivenOcelotIsRunningWithNoWebHostBuilder(string baseUrl) - { - _webHostBuilder = new WebHostBuilder() - .UseUrls(_ocelotBaseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(x => { - x.AddSingleton(_webHostBuilder); - x.AddOcelot() - .AddCacheManager(c => - { - c.WithDictionaryHandle(); - }) - .AddAdministration("/administration", "secret"); - }) - .Configure(app => { - app.UseOcelot().Wait(); - }); - - _builder = _webHostBuilder.Build(); - - _builder.Start(); - } - - private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) - { - var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json"; - - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - - var text = File.ReadAllText(configurationPath); - - configurationPath = $"{AppContext.BaseDirectory}/ocelot.json"; - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - - text = File.ReadAllText(configurationPath); - } - - private void WhenIGetUrlOnTheApiGateway(string url) - { - _response = _httpClient.GetAsync(url).Result; - } - - private void WhenIDeleteOnTheApiGateway(string url) - { - _response = _httpClient.DeleteAsync(url).Result; - } - - private void ThenTheStatusCodeShouldBe(HttpStatusCode expectedHttpStatusCode) - { - _response.StatusCode.ShouldBe(expectedHttpStatusCode); - } - - public void Dispose() - { - Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE", ""); - Environment.SetEnvironmentVariable("OCELOT_CERTIFICATE_PASSWORD", ""); - _builder?.Dispose(); - _httpClient?.Dispose(); - _identityServerBuilder?.Dispose(); - } - - private void GivenThereIsAFooServiceRunningOn(string baseUrl) - { - _fooServiceBuilder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => - { - app.UsePathBase("/foo"); - app.Run(async context => - { - context.Response.StatusCode = 200; - await context.Response.WriteAsync("foo"); - }); - }) - .Build(); - - _fooServiceBuilder.Start(); - } - - private void GivenThereIsABarServiceRunningOn(string baseUrl) - { - _barServiceBuilder = new WebHostBuilder() - .UseUrls(baseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => - { - app.UsePathBase("/bar"); - app.Run(async context => - { - context.Response.StatusCode = 200; - await context.Response.WriteAsync("bar"); - }); - }) - .Build(); - - _barServiceBuilder.Start(); - } - } -} diff --git a/test/Ocelot.IntegrationTests/BearerToken.cs b/test/Ocelot.IntegrationTests/BearerToken.cs deleted file mode 100644 index 02180844..00000000 --- a/test/Ocelot.IntegrationTests/BearerToken.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Newtonsoft.Json; - -namespace Ocelot.IntegrationTests -{ - 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; } - } -} diff --git a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj index 19bf854f..716744f1 100644 --- a/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj +++ b/test/Ocelot.IntegrationTests/Ocelot.IntegrationTests.csproj @@ -39,11 +39,8 @@ - - - diff --git a/test/Ocelot.IntegrationTests/RaftTests.cs b/test/Ocelot.IntegrationTests/RaftTests.cs deleted file mode 100644 index 7afa3ae4..00000000 --- a/test/Ocelot.IntegrationTests/RaftTests.cs +++ /dev/null @@ -1,515 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Ocelot.Raft; -using Rafty.Concensus; -using Rafty.Infrastructure; -using Shouldly; -using Xunit; -using static Rafty.Infrastructure.Wait; -using Microsoft.Data.Sqlite; -using Microsoft.Extensions.Configuration; -using Ocelot.DependencyInjection; -using Ocelot.Middleware; - -namespace Ocelot.IntegrationTests -{ - using System.Threading.Tasks; - using Xunit.Abstractions; - - public class RaftTests : IDisposable - { - private readonly List _builders; - private readonly List _webHostBuilders; - private readonly List _threads; - private FilePeers _peers; - private HttpClient _httpClient; - private readonly HttpClient _httpClientForAssertions; - private BearerToken _token; - private HttpResponseMessage _response; - private static readonly object _lock = new object(); - private ITestOutputHelper _output; - - public RaftTests(ITestOutputHelper output) - { - _output = output; - _httpClientForAssertions = new HttpClient(); - _webHostBuilders = new List(); - _builders = new List(); - _threads = new List(); - } - - [Fact(Skip = "Still not stable, more work required in rafty..")] - public async Task should_persist_command_to_five_servers() - { - var peers = new List - { - new FilePeer {HostAndPort = "http://localhost:5000"}, - - new FilePeer {HostAndPort = "http://localhost:5001"}, - - new FilePeer {HostAndPort = "http://localhost:5002"}, - - new FilePeer {HostAndPort = "http://localhost:5003"}, - - new FilePeer {HostAndPort = "http://localhost:5004"} - }; - - var configuration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - } - }; - - var updatedConfiguration = new FileConfiguration - { - GlobalConfiguration = new FileGlobalConfiguration - { - }, - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "127.0.0.1", - Port = 80, - } - }, - DownstreamScheme = "http", - DownstreamPathTemplate = "/geoffrey", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/" - }, - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "123.123.123", - Port = 443, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/blooper/{productId}", - UpstreamHttpMethod = new List { "post" }, - UpstreamPathTemplate = "/test" - } - } - }; - - var command = new UpdateFileConfiguration(updatedConfiguration); - GivenThePeersAre(peers); - GivenThereIsAConfiguration(configuration); - GivenFiveServersAreRunning(); - await GivenIHaveAnOcelotToken("/administration"); - await WhenISendACommandIntoTheCluster(command); - Thread.Sleep(5000); - await ThenTheCommandIsReplicatedToAllStateMachines(command); - } - - [Fact(Skip = "Still not stable, more work required in rafty..")] - public async Task should_persist_command_to_five_servers_when_using_administration_api() - { - var peers = new List - { - new FilePeer {HostAndPort = "http://localhost:5005"}, - - new FilePeer {HostAndPort = "http://localhost:5006"}, - - new FilePeer {HostAndPort = "http://localhost:5007"}, - - new FilePeer {HostAndPort = "http://localhost:5008"}, - - new FilePeer {HostAndPort = "http://localhost:5009"} - }; - - var configuration = new FileConfiguration - { - }; - - var updatedConfiguration = new FileConfiguration - { - ReRoutes = new List() - { - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "127.0.0.1", - Port = 80, - } - }, - DownstreamScheme = "http", - DownstreamPathTemplate = "/geoffrey", - UpstreamHttpMethod = new List { "get" }, - UpstreamPathTemplate = "/" - }, - new FileReRoute() - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "123.123.123", - Port = 443, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/blooper/{productId}", - UpstreamHttpMethod = new List { "post" }, - UpstreamPathTemplate = "/test" - } - } - }; - - var command = new UpdateFileConfiguration(updatedConfiguration); - GivenThePeersAre(peers); - GivenThereIsAConfiguration(configuration); - GivenFiveServersAreRunning(); - await GivenIHaveAnOcelotToken("/administration"); - GivenIHaveAddedATokenToMyRequest(); - await WhenIPostOnTheApiGateway("/administration/configuration", updatedConfiguration); - await ThenTheCommandIsReplicatedToAllStateMachines(command); - } - - private void GivenThePeersAre(List peers) - { - FilePeers filePeers = new FilePeers(); - filePeers.Peers.AddRange(peers); - var json = JsonConvert.SerializeObject(filePeers); - File.WriteAllText("peers.json", json); - _httpClient = new HttpClient(); - var ocelotBaseUrl = peers[0].HostAndPort; - _httpClient.BaseAddress = new Uri(ocelotBaseUrl); - } - - private async Task WhenISendACommandIntoTheCluster(UpdateFileConfiguration command) - { - async Task SendCommand() - { - try - { - var p = _peers.Peers.First(); - var json = JsonConvert.SerializeObject(command, new JsonSerializerSettings() - { - TypeNameHandling = TypeNameHandling.All - }); - var httpContent = new StringContent(json); - httpContent.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - using (var httpClient = new HttpClient()) - { - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - var response = await httpClient.PostAsync($"{p.HostAndPort}/administration/raft/command", httpContent); - response.EnsureSuccessStatusCode(); - var content = await response.Content.ReadAsStringAsync(); - - var errorResult = JsonConvert.DeserializeObject>(content); - - if (!string.IsNullOrEmpty(errorResult.Error)) - { - return false; - } - - var okResult = JsonConvert.DeserializeObject>(content); - - if (okResult.Command.Configuration.ReRoutes.Count == 2) - { - return true; - } - } - - return false; - } - catch (Exception e) - { - Console.WriteLine(e); - return false; - } - } - - var commandSent = await WaitFor(40000).Until(async () => - { - var result = await SendCommand(); - Thread.Sleep(1000); - return result; - }); - - commandSent.ShouldBeTrue(); - } - - private async Task ThenTheCommandIsReplicatedToAllStateMachines(UpdateFileConfiguration expecteds) - { - async Task CommandCalledOnAllStateMachines() - { - try - { - var passed = 0; - foreach (var peer in _peers.Peers) - { - var path = $"{peer.HostAndPort.Replace("/","").Replace(":","")}.db"; - using(var connection = new SqliteConnection($"Data Source={path};")) - { - connection.Open(); - var sql = @"select count(id) from logs"; - using(var command = new SqliteCommand(sql, connection)) - { - var index = Convert.ToInt32(command.ExecuteScalar()); - index.ShouldBe(1); - } - } - - _httpClientForAssertions.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - var result = await _httpClientForAssertions.GetAsync($"{peer.HostAndPort}/administration/configuration"); - var json = await result.Content.ReadAsStringAsync(); - var response = JsonConvert.DeserializeObject(json, new JsonSerializerSettings{TypeNameHandling = TypeNameHandling.All}); - response.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.Configuration.GlobalConfiguration.RequestIdKey); - response.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.Configuration.GlobalConfiguration.ServiceDiscoveryProvider.Host); - response.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.Configuration.GlobalConfiguration.ServiceDiscoveryProvider.Port); - - for (var i = 0; i < response.ReRoutes.Count; i++) - { - for (var j = 0; j < response.ReRoutes[i].DownstreamHostAndPorts.Count; j++) - { - var res = response.ReRoutes[i].DownstreamHostAndPorts[j]; - var expected = expecteds.Configuration.ReRoutes[i].DownstreamHostAndPorts[j]; - res.Host.ShouldBe(expected.Host); - res.Port.ShouldBe(expected.Port); - } - - response.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.Configuration.ReRoutes[i].DownstreamPathTemplate); - response.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.Configuration.ReRoutes[i].DownstreamScheme); - response.ReRoutes[i].UpstreamPathTemplate.ShouldBe(expecteds.Configuration.ReRoutes[i].UpstreamPathTemplate); - response.ReRoutes[i].UpstreamHttpMethod.ShouldBe(expecteds.Configuration.ReRoutes[i].UpstreamHttpMethod); - } - - passed++; - } - - return passed == 5; - } - catch(Exception e) - { - //_output.WriteLine($"{e.Message}, {e.StackTrace}"); - Console.WriteLine(e); - return false; - } - } - - var commandOnAllStateMachines = await WaitFor(40000).Until(async () => - { - var result = await CommandCalledOnAllStateMachines(); - Thread.Sleep(1000); - return result; - }); - - commandOnAllStateMachines.ShouldBeTrue(); - } - - private async Task WhenIPostOnTheApiGateway(string url, FileConfiguration updatedConfiguration) - { - async Task SendCommand() - { - var json = JsonConvert.SerializeObject(updatedConfiguration); - - var content = new StringContent(json); - - content.Headers.ContentType = new MediaTypeHeaderValue("application/json"); - - _response = await _httpClient.PostAsync(url, content); - - var responseContent = await _response.Content.ReadAsStringAsync(); - - if(responseContent == "There was a problem. This error message sucks raise an issue in GitHub.") - { - return false; - } - - if(string.IsNullOrEmpty(responseContent)) - { - return false; - } - - return _response.IsSuccessStatusCode; - } - - var commandSent = await WaitFor(40000).Until(async () => - { - var result = await SendCommand(); - Thread.Sleep(1000); - return result; - }); - - commandSent.ShouldBeTrue(); - } - - private void GivenIHaveAddedATokenToMyRequest() - { - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", _token.AccessToken); - } - - private async Task GivenIHaveAnOcelotToken(string adminPath) - { - async Task AddToken() - { - try - { - var tokenUrl = $"{adminPath}/connect/token"; - var formData = new List> - { - new KeyValuePair("client_id", "admin"), - new KeyValuePair("client_secret", "secret"), - new KeyValuePair("scope", "admin"), - new KeyValuePair("grant_type", "client_credentials") - }; - var content = new FormUrlEncodedContent(formData); - - var response = await _httpClient.PostAsync(tokenUrl, content); - var responseContent = await response.Content.ReadAsStringAsync(); - if(!response.IsSuccessStatusCode) - { - return false; - } - - _token = JsonConvert.DeserializeObject(responseContent); - var configPath = $"{adminPath}/.well-known/openid-configuration"; - response = await _httpClient.GetAsync(configPath); - return response.IsSuccessStatusCode; - } - catch(Exception) - { - return false; - } - } - - var addToken = await WaitFor(40000).Until(async () => - { - var result = await AddToken(); - Thread.Sleep(1000); - return result; - }); - - addToken.ShouldBeTrue(); - } - - private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) - { - var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json"; - - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - - var text = File.ReadAllText(configurationPath); - - configurationPath = $"{AppContext.BaseDirectory}/ocelot.json"; - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - - text = File.ReadAllText(configurationPath); - } - - private void GivenAServerIsRunning(string url) - { - lock(_lock) - { - IWebHostBuilder webHostBuilder = new WebHostBuilder(); - webHostBuilder.UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); - config.AddJsonFile("peers.json", optional: true, reloadOnChange: true); - #pragma warning disable CS0618 - config.AddOcelotBaseUrl(url); - #pragma warning restore CS0618 - config.AddEnvironmentVariables(); - }) - .ConfigureServices(x => - { - x.AddSingleton(new NodeId(url)); - x - .AddOcelot() - .AddAdministration("/administration", "secret") - .AddRafty(); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - - var builder = webHostBuilder.Build(); - builder.Start(); - - _webHostBuilders.Add(webHostBuilder); - _builders.Add(builder); - } - } - - private void GivenFiveServersAreRunning() - { - var bytes = File.ReadAllText("peers.json"); - _peers = JsonConvert.DeserializeObject(bytes); - - foreach (var peer in _peers.Peers) - { - File.Delete(peer.HostAndPort.Replace("/", "").Replace(":", "")); - File.Delete($"{peer.HostAndPort.Replace("/", "").Replace(":", "")}.db"); - var thread = new Thread(() => GivenAServerIsRunning(peer.HostAndPort)); - thread.Start(); - _threads.Add(thread); - } - } - - public void Dispose() - { - foreach (var builder in _builders) - { - builder?.Dispose(); - } - - foreach (var peer in _peers.Peers) - { - try - { - File.Delete(peer.HostAndPort.Replace("/", "").Replace(":", "")); - File.Delete($"{peer.HostAndPort.Replace("/", "").Replace(":", "")}.db"); - } - catch (Exception e) - { - Console.WriteLine(e); - } - } - } - } -} diff --git a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs index ed125aa4..1e9c0dd8 100644 --- a/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs +++ b/test/Ocelot.IntegrationTests/ThreadSafeHeadersTests.cs @@ -1,218 +1,205 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net.Http; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Newtonsoft.Json; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; -using Microsoft.AspNetCore.Http; -using System.Threading.Tasks; -using System.Collections.Concurrent; -using CacheManager.Core; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Ocelot.DependencyInjection; -using Ocelot.Middleware; - -namespace Ocelot.IntegrationTests -{ - public class ThreadSafeHeadersTests : IDisposable - { - private readonly HttpClient _httpClient; - private IWebHost _builder; - private IWebHostBuilder _webHostBuilder; - private readonly string _ocelotBaseUrl; - private IWebHost _downstreamBuilder; - private readonly Random _random; - private readonly ConcurrentBag _results; - - public ThreadSafeHeadersTests() - { - _results = new ConcurrentBag(); - _random = new Random(); - _httpClient = new HttpClient(); - _ocelotBaseUrl = "http://localhost:5001"; - _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); - } - - [Fact] - public void should_return_same_response_for_each_different_header_under_load_to_downsteam_service() - { - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 51879, - } - }, - UpstreamPathTemplate = "/", - UpstreamHttpMethod = new List { "Get" }, - } - } - }; - - this.Given(x => GivenThereIsAConfiguration(configuration)) - .And(x => GivenThereIsAServiceRunningOn("http://localhost:51879")) - .And(x => GivenOcelotIsRunning()) - .When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 300)) - .Then(x => ThenTheSameHeaderValuesAreReturnedByTheDownstreamService()) - .BDDfy(); - } - - private void GivenThereIsAServiceRunningOn(string url) - { - _downstreamBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - var header = context.Request.Headers["ThreadSafeHeadersTest"]; - - context.Response.StatusCode = 200; - await context.Response.WriteAsync(header[0]); - }); - }) - .Build(); - - _downstreamBuilder.Start(); - } - - private void GivenOcelotIsRunning() - { - _webHostBuilder = new WebHostBuilder() - .UseUrls(_ocelotBaseUrl) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - config.AddJsonFile("ocelot.json"); - config.AddEnvironmentVariables(); - }) - .ConfigureServices(x => - { - Action settings = (s) => - { - s.WithMicrosoftLogging(log => - { - log.AddConsole(LogLevel.Debug); - }) - .WithDictionaryHandle(); - }; - - x.AddOcelot() - .AddCacheManager(settings) - .AddAdministration("/administration", "secret"); - }) - .Configure(app => - { - app.UseOcelot().Wait(); - }); - - _builder = _webHostBuilder.Build(); - - _builder.Start(); - } - - private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) - { - var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json"; - - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - - var text = File.ReadAllText(configurationPath); - - configurationPath = $"{AppContext.BaseDirectory}/ocelot.json"; - - if (File.Exists(configurationPath)) - { - File.Delete(configurationPath); - } - - File.WriteAllText(configurationPath, jsonConfiguration); - - text = File.ReadAllText(configurationPath); - } - - private void WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues(string url, int times) - { - var tasks = new Task[times]; - - for (int i = 0; i < times; i++) - { - var urlCopy = url; - var random = _random.Next(0, 50); - tasks[i] = GetForThreadSafeHeadersTest(urlCopy, random); - } - - Task.WaitAll(tasks); - } - - private async Task GetForThreadSafeHeadersTest(string url, int random) - { - var request = new HttpRequestMessage(HttpMethod.Get, url); - request.Headers.Add("ThreadSafeHeadersTest", new List { random.ToString() }); - var response = await _httpClient.SendAsync(request); - var content = await response.Content.ReadAsStringAsync(); - int result = int.Parse(content); - var tshtr = new ThreadSafeHeadersTestResult(result, random); - _results.Add(tshtr); - } - - private void ThenTheSameHeaderValuesAreReturnedByTheDownstreamService() - { - foreach(var result in _results) - { - result.Result.ShouldBe(result.Random); - } - } - - public void Dispose() - { - _builder?.Dispose(); - _httpClient?.Dispose(); - _downstreamBuilder?.Dispose(); - } - - class ThreadSafeHeadersTestResult - { - public ThreadSafeHeadersTestResult(int result, int random) - { - Result = result; - Random = random; - } - - public int Result { get; private set; } - public int Random { get; private set; } - } - } -} +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.Http; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Newtonsoft.Json; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; +using Microsoft.AspNetCore.Http; +using System.Threading.Tasks; +using System.Collections.Concurrent; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Ocelot.DependencyInjection; +using Ocelot.Middleware; + +namespace Ocelot.IntegrationTests +{ + public class ThreadSafeHeadersTests : IDisposable + { + private readonly HttpClient _httpClient; + private IWebHost _builder; + private IWebHostBuilder _webHostBuilder; + private readonly string _ocelotBaseUrl; + private IWebHost _downstreamBuilder; + private readonly Random _random; + private readonly ConcurrentBag _results; + + public ThreadSafeHeadersTests() + { + _results = new ConcurrentBag(); + _random = new Random(); + _httpClient = new HttpClient(); + _ocelotBaseUrl = "http://localhost:5001"; + _httpClient.BaseAddress = new Uri(_ocelotBaseUrl); + } + + [Fact] + public void should_return_same_response_for_each_different_header_under_load_to_downsteam_service() + { + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 51879, + } + }, + UpstreamPathTemplate = "/", + UpstreamHttpMethod = new List { "Get" }, + } + } + }; + + this.Given(x => GivenThereIsAConfiguration(configuration)) + .And(x => GivenThereIsAServiceRunningOn("http://localhost:51879")) + .And(x => GivenOcelotIsRunning()) + .When(x => WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues("/", 300)) + .Then(x => ThenTheSameHeaderValuesAreReturnedByTheDownstreamService()) + .BDDfy(); + } + + private void GivenThereIsAServiceRunningOn(string url) + { + _downstreamBuilder = new WebHostBuilder() + .UseUrls(url) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .UseUrls(url) + .Configure(app => + { + app.Run(async context => + { + var header = context.Request.Headers["ThreadSafeHeadersTest"]; + + context.Response.StatusCode = 200; + await context.Response.WriteAsync(header[0]); + }); + }) + .Build(); + + _downstreamBuilder.Start(); + } + + private void GivenOcelotIsRunning() + { + _webHostBuilder = new WebHostBuilder() + .UseUrls(_ocelotBaseUrl) + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureAppConfiguration((hostingContext, config) => + { + config.SetBasePath(hostingContext.HostingEnvironment.ContentRootPath); + var env = hostingContext.HostingEnvironment; + config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: false) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: false); + config.AddJsonFile("ocelot.json", false, false); + config.AddEnvironmentVariables(); + }) + .ConfigureServices(x => + { + x.AddOcelot(); + }) + .Configure(app => + { + app.UseOcelot().Wait(); + }); + + _builder = _webHostBuilder.Build(); + + _builder.Start(); + } + + private void GivenThereIsAConfiguration(FileConfiguration fileConfiguration) + { + var configurationPath = $"{Directory.GetCurrentDirectory()}/ocelot.json"; + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration); + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + + var text = File.ReadAllText(configurationPath); + + configurationPath = $"{AppContext.BaseDirectory}/ocelot.json"; + + if (File.Exists(configurationPath)) + { + File.Delete(configurationPath); + } + + File.WriteAllText(configurationPath, jsonConfiguration); + + text = File.ReadAllText(configurationPath); + } + + private void WhenIGetUrlOnTheApiGatewayMultipleTimesWithDifferentHeaderValues(string url, int times) + { + var tasks = new Task[times]; + + for (int i = 0; i < times; i++) + { + var urlCopy = url; + var random = _random.Next(0, 50); + tasks[i] = GetForThreadSafeHeadersTest(urlCopy, random); + } + + Task.WaitAll(tasks); + } + + private async Task GetForThreadSafeHeadersTest(string url, int random) + { + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Add("ThreadSafeHeadersTest", new List { random.ToString() }); + var response = await _httpClient.SendAsync(request); + var content = await response.Content.ReadAsStringAsync(); + int result = int.Parse(content); + var tshtr = new ThreadSafeHeadersTestResult(result, random); + _results.Add(tshtr); + } + + private void ThenTheSameHeaderValuesAreReturnedByTheDownstreamService() + { + foreach(var result in _results) + { + result.Result.ShouldBe(result.Random); + } + } + + public void Dispose() + { + _builder?.Dispose(); + _httpClient?.Dispose(); + _downstreamBuilder?.Dispose(); + } + + class ThreadSafeHeadersTestResult + { + public ThreadSafeHeadersTestResult(int result, int random) + { + Result = result; + Random = random; + } + + public int Result { get; private set; } + public int Random { get; private set; } + } + } +} diff --git a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj index d9ddff9c..5c3c5cfe 100644 --- a/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj +++ b/test/Ocelot.ManualTest/Ocelot.ManualTest.csproj @@ -36,7 +36,6 @@ - all diff --git a/test/Ocelot.ManualTest/Program.cs b/test/Ocelot.ManualTest/Program.cs index fede584f..2a43e886 100644 --- a/test/Ocelot.ManualTest/Program.cs +++ b/test/Ocelot.ManualTest/Program.cs @@ -8,7 +8,6 @@ using Ocelot.DependencyInjection; using Ocelot.Middleware; using System; - using IdentityServer4.AccessTokenValidation; using System.Net.Http; using System.Threading.Tasks; using System.Threading; @@ -26,7 +25,7 @@ .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) .AddJsonFile("appsettings.json", true, true) .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("ocelot.json") + .AddJsonFile("ocelot.json", false, false) .AddEnvironmentVariables(); }) .ConfigureServices(s => { @@ -38,17 +37,17 @@ }); s.AddOcelot() - .AddDelegatingHandler(true) + .AddDelegatingHandler(true); // .AddCacheManager(x => // { // x.WithDictionaryHandle(); // }) - /*.AddOpenTracing(option => - { - option.CollectorUrl = "http://localhost:9618"; - option.Service = "Ocelot.ManualTest"; - })*/ - .AddAdministration("/administration", "secret"); + // .AddOpenTracing(option => + // { + // option.CollectorUrl = "http://localhost:9618"; + // option.Service = "Ocelot.ManualTest"; + // }) + // .AddAdministration("/administration", "secret"); }) .ConfigureLogging((hostingContext, logging) => { diff --git a/test/Ocelot.ManualTest/ocelot.json b/test/Ocelot.ManualTest/ocelot.json index f53ef507..322c607b 100644 --- a/test/Ocelot.ManualTest/ocelot.json +++ b/test/Ocelot.ManualTest/ocelot.json @@ -8,7 +8,7 @@ "DownstreamHostAndPorts": [ { "Host": "localhost", - "Port": 3000 + "Port": 5001 } ], "QoSOptions": { diff --git a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs index e287d5f4..d9ad9768 100644 --- a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs @@ -1,91 +1,94 @@ -using Ocelot.Configuration; -using Ocelot.Middleware; - -namespace Ocelot.UnitTests.Authentication -{ - using System.Collections.Generic; - using System.IO; - using System.Text; - using System.Threading.Tasks; - using Microsoft.AspNetCore.Http; - using Moq; - using Ocelot.Authentication.Middleware; - using Ocelot.Configuration.Builder; - using Ocelot.Logging; - using Shouldly; - using TestStack.BDDfy; - using Xunit; - - public class AuthenticationMiddlewareTests - { - private AuthenticationMiddleware _middleware; - private Mock _factory; - private Mock _logger; - private OcelotRequestDelegate _next; - private DownstreamContext _downstreamContext; - - public AuthenticationMiddlewareTests() - { - _factory = new Mock(); - _logger = new Mock(); - _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - } - - [Fact] - public void should_call_next_middleware_if_route_is_not_authenticated() - { - this.Given(x => GivenTheDownStreamRouteIs( - new DownstreamReRouteBuilder().WithUpstreamHttpMethod(new List { "Get" }).Build())) - .And(x => GivenTheTestServerPipelineIsConfigured()) - .When(x => WhenICallTheMiddleware()) - .Then(x => ThenTheUserIsAuthenticated()) - .BDDfy(); - } - - private void WhenICallTheMiddleware() - { - _next = (context) => { - byte[] byteArray = Encoding.ASCII.GetBytes("The user is authenticated"); - var stream = new MemoryStream(byteArray); - context.HttpContext.Response.Body = stream; - return Task.CompletedTask; - }; - _middleware = new AuthenticationMiddleware(_next, _factory.Object); - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - } - - private void GivenTheTestServerPipelineIsConfigured() - { - _next = (context) => { - byte[] byteArray = Encoding.ASCII.GetBytes("The user is authenticated"); - var stream = new MemoryStream(byteArray); - context.HttpContext.Response.Body = stream; - return Task.CompletedTask; - }; - } - - private void ThenTheUserIsAuthenticated() - { - var content = _downstreamContext.HttpContext.Response.Body.AsString(); - content.ShouldBe("The user is authenticated"); - } - - private void GivenTheDownStreamRouteIs(DownstreamReRoute downstreamRoute) - { - _downstreamContext.DownstreamReRoute = downstreamRoute; - } - } - - public static class StreamExtensions - { - public static string AsString(this Stream stream) - { - using(var reader = new StreamReader(stream)) - { - string text = reader.ReadToEnd(); - return text; - } - } - } -} +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] + +namespace Ocelot.UnitTests.Authentication +{ + using System.Collections.Generic; + using System.IO; + using System.Text; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Moq; + using Ocelot.Authentication.Middleware; + using Ocelot.Configuration.Builder; + using Ocelot.Logging; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + using Ocelot.Configuration; + using Ocelot.Middleware; + + public class AuthenticationMiddlewareTests + { + private AuthenticationMiddleware _middleware; + private readonly Mock _factory; + private Mock _logger; + private OcelotRequestDelegate _next; + private readonly DownstreamContext _downstreamContext; + + public AuthenticationMiddlewareTests() + { + _factory = new Mock(); + _logger = new Mock(); + _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + } + + [Fact] + public void should_call_next_middleware_if_route_is_not_authenticated() + { + this.Given(x => GivenTheDownStreamRouteIs( + new DownstreamReRouteBuilder().WithUpstreamHttpMethod(new List { "Get" }).Build())) + .And(x => GivenTheTestServerPipelineIsConfigured()) + .When(x => WhenICallTheMiddleware()) + .Then(x => ThenTheUserIsAuthenticated()) + .BDDfy(); + } + + private void WhenICallTheMiddleware() + { + _next = (context) => { + byte[] byteArray = Encoding.ASCII.GetBytes("The user is authenticated"); + var stream = new MemoryStream(byteArray); + context.HttpContext.Response.Body = stream; + return Task.CompletedTask; + }; + _middleware = new AuthenticationMiddleware(_next, _factory.Object); + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + } + + private void GivenTheTestServerPipelineIsConfigured() + { + _next = (context) => { + byte[] byteArray = Encoding.ASCII.GetBytes("The user is authenticated"); + var stream = new MemoryStream(byteArray); + context.HttpContext.Response.Body = stream; + return Task.CompletedTask; + }; + } + + private void ThenTheUserIsAuthenticated() + { + var content = _downstreamContext.HttpContext.Response.Body.AsString(); + content.ShouldBe("The user is authenticated"); + } + + private void GivenTheDownStreamRouteIs(DownstreamReRoute downstreamRoute) + { + _downstreamContext.DownstreamReRoute = downstreamRoute; + } + } + + public static class StreamExtensions + { + public static string AsString(this Stream stream) + { + using(var reader = new StreamReader(stream)) + { + string text = reader.ReadToEnd(); + return text; + } + } + } +} diff --git a/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs b/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs deleted file mode 100644 index 4b891d73..00000000 --- a/test/Ocelot.UnitTests/Cache/CacheManagerCacheTests.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using CacheManager.Core; -using Moq; -using Ocelot.Cache; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Cache -{ - public class CacheManagerCacheTests - { - private OcelotCacheManagerCache _ocelotOcelotCacheManager; - private Mock> _mockCacheManager; - private string _key; - private string _value; - private string _resultGet; - private TimeSpan _ttlSeconds; - private string _region; - - public CacheManagerCacheTests() - { - _mockCacheManager = new Mock>(); - _ocelotOcelotCacheManager = new OcelotCacheManagerCache(_mockCacheManager.Object); - } - - [Fact] - public void should_get_from_cache() - { - this.Given(x => x.GivenTheFollowingIsCached("someKey", "someRegion", "someValue")) - .When(x => x.WhenIGetFromTheCache()) - .Then(x => x.ThenTheResultIs("someValue")) - .BDDfy(); - } - - [Fact] - public void should_add_to_cache() - { - this.When(x => x.WhenIAddToTheCache("someKey", "someValue", TimeSpan.FromSeconds(1))) - .Then(x => x.ThenTheCacheIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_delete_key_from_cache() - { - this.Given(_ => GivenTheFollowingRegion("fookey")) - .When(_ => WhenIDeleteTheRegion("fookey")) - .Then(_ => ThenTheRegionIsDeleted("fookey")) - .BDDfy(); - } - - private void WhenIDeleteTheRegion(string region) - { - _ocelotOcelotCacheManager.ClearRegion(region); - } - - private void ThenTheRegionIsDeleted(string region) - { - _mockCacheManager - .Verify(x => x.ClearRegion(region), Times.Once); - } - - private void GivenTheFollowingRegion(string key) - { - _ocelotOcelotCacheManager.Add(key, "doesnt matter", TimeSpan.FromSeconds(10), "region"); - } - - private void WhenIAddToTheCache(string key, string value, TimeSpan ttlSeconds) - { - _key = key; - _value = value; - _ttlSeconds = ttlSeconds; - _ocelotOcelotCacheManager.Add(_key, _value, _ttlSeconds, "region"); - } - - private void ThenTheCacheIsCalledCorrectly() - { - _mockCacheManager - .Verify(x => x.Add(It.IsAny>()), Times.Once); - } - - private void ThenTheResultIs(string expected) - { - _resultGet.ShouldBe(expected); - } - - private void WhenIGetFromTheCache() - { - _resultGet = _ocelotOcelotCacheManager.Get(_key, _region); - } - - private void GivenTheFollowingIsCached(string key, string region, string value) - { - _key = key; - _value = value; - _region = region; - _mockCacheManager - .Setup(x => x.Get(It.IsAny(), It.IsAny())) - .Returns(value); - } - } -} diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs deleted file mode 100644 index b3bbface..00000000 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareRealCacheTests.cs +++ /dev/null @@ -1,95 +0,0 @@ -namespace Ocelot.UnitTests.Cache -{ - using System.Linq; - using System.Net; - using System.Net.Http.Headers; - using CacheManager.Core; - using Shouldly; - using System.Collections.Generic; - using System.Net.Http; - using System.Threading.Tasks; - using Moq; - using Ocelot.Cache; - using Ocelot.Cache.Middleware; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using Ocelot.Logging; - using Ocelot.Middleware; - using TestStack.BDDfy; - using Xunit; - using Microsoft.AspNetCore.Http; - using Ocelot.Middleware.Multiplexer; - - public class OutputCacheMiddlewareRealCacheTests - { - private readonly IOcelotCache _cacheManager; - private readonly OutputCacheMiddleware _middleware; - private readonly DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; - private Mock _loggerFactory; - private IRegionCreator _regionCreator; - private Mock _logger; - - public OutputCacheMiddlewareRealCacheTests() - { - _loggerFactory = new Mock(); - _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _regionCreator = new RegionCreator(); - var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", x => - { - x.WithDictionaryHandle(); - }); - _cacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - _downstreamContext.DownstreamRequest = new Ocelot.Request.Middleware.DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123")); - _next = context => Task.CompletedTask; - _middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cacheManager, _regionCreator); - } - - [Fact] - public void should_cache_content_headers() - { - var content = new StringContent("{\"Test\": 1}") - { - Headers = { ContentType = new MediaTypeHeaderValue("application/json")} - }; - - var response = new DownstreamResponse(content, HttpStatusCode.OK, new List>>()); - - this.Given(x => x.GivenResponseIsNotCached(response)) - .And(x => x.GivenTheDownstreamRouteIs()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheContentTypeHeaderIsCached()) - .BDDfy(); - } - - private void WhenICallTheMiddleware() - { - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - } - - private void ThenTheContentTypeHeaderIsCached() - { - var result = _cacheManager.Get("GET-https://some.url/blah?abcd=123", "kanken"); - var header = result.ContentHeaders["Content-Type"]; - header.First().ShouldBe("application/json"); - } - - private void GivenResponseIsNotCached(DownstreamResponse response) - { - _downstreamContext.DownstreamResponse = response; - } - - private void GivenTheDownstreamRouteIs() - { - var reRoute = new DownstreamReRouteBuilder() - .WithIsCached(true) - .WithCacheOptions(new CacheOptions(100, "kanken")) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build(); - - _downstreamContext.DownstreamReRoute = reRoute; - } - } -} diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs index c0b59350..d13b22f2 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -16,24 +16,22 @@ using TestStack.BDDfy; using Xunit; using System.Net; + using Microsoft.Extensions.DependencyInjection; using Ocelot.Middleware; - using Ocelot.Middleware.Multiplexer; public class OutputCacheMiddlewareTests { - private readonly Mock> _cacheManager; + private readonly Mock> _cache; private readonly Mock _loggerFactory; private Mock _logger; private OutputCacheMiddleware _middleware; private readonly DownstreamContext _downstreamContext; private readonly OcelotRequestDelegate _next; private CachedResponse _response; - private readonly IRegionCreator _regionCreator; public OutputCacheMiddlewareTests() { - _cacheManager = new Mock>(); - _regionCreator = new RegionCreator(); + _cache = new Mock>(); _downstreamContext = new DownstreamContext(new DefaultHttpContext()); _loggerFactory = new Mock(); _logger = new Mock(); @@ -91,14 +89,14 @@ private void WhenICallTheMiddleware() { - _middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cacheManager.Object, _regionCreator); + _middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cache.Object); _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); } private void GivenThereIsACachedResponse(CachedResponse response) { _response = response; - _cacheManager + _cache .Setup(x => x.Get(It.IsAny(), It.IsAny())) .Returns(_response); } @@ -127,13 +125,13 @@ private void ThenTheCacheGetIsCalledCorrectly() { - _cacheManager + _cache .Verify(x => x.Get(It.IsAny(), It.IsAny()), Times.Once); } private void ThenTheCacheAddIsCalledCorrectly() { - _cacheManager + _cache .Verify(x => x.Add(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } } diff --git a/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationRepositoryTests.cs deleted file mode 100644 index d26a69fc..00000000 --- a/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationRepositoryTests.cs +++ /dev/null @@ -1,263 +0,0 @@ -namespace Ocelot.UnitTests.Configuration -{ - using Xunit; - using TestStack.BDDfy; - using Shouldly; - using Ocelot.Configuration.Repository; - using Moq; - using Ocelot.Infrastructure.Consul; - using Ocelot.Logging; - using Ocelot.Configuration.File; - using Ocelot.Cache; - using System; - using System.Collections.Generic; - using Ocelot.Responses; - using System.Threading.Tasks; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using Ocelot.ServiceDiscovery.Configuration; - using Consul; - using Newtonsoft.Json; - using System.Text; - using System.Threading; - using System.Linq; - - public class ConsulFileConfigurationRepositoryTests - { - private ConsulFileConfigurationRepository _repo; - private Mock> _cache; - private Mock _internalRepo; - private Mock _factory; - private Mock _loggerFactory; - private Mock _client; - private Mock _kvEndpoint; - private FileConfiguration _fileConfiguration; - private Response _setResult; - private Response _getResult; - - public ConsulFileConfigurationRepositoryTests() - { - _cache = new Mock>(); - _internalRepo = new Mock(); - _loggerFactory = new Mock(); - - _factory = new Mock(); - _client = new Mock(); - _kvEndpoint = new Mock(); - - _client - .Setup(x => x.KV) - .Returns(_kvEndpoint.Object); - - _factory - .Setup(x => x.Get(It.IsAny())) - .Returns(_client.Object); - - _internalRepo - .Setup(x => x.Get()) - .Returns(new OkResponse(new InternalConfiguration(new List(), "", new ServiceProviderConfigurationBuilder().Build(), "", It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))); - - _repo = new ConsulFileConfigurationRepository(_cache.Object, _internalRepo.Object, _factory.Object, _loggerFactory.Object); - } - - [Fact] - public void should_set_config() - { - var config = FakeFileConfiguration(); - - this.Given(_ => GivenIHaveAConfiguration(config)) - .And(_ => GivenWritingToConsulSucceeds()) - .When(_ => WhenISetTheConfiguration()) - .Then(_ => ThenTheConfigurationIsStoredAs(config)) - .BDDfy(); - } - - [Fact] - public void should_get_config() - { - var config = FakeFileConfiguration(); - - this.Given(_ => GivenIHaveAConfiguration(config)) - .And(_ => GivenFetchFromConsulSucceeds()) - .When(_ => WhenIGetTheConfiguration()) - .Then(_ => ThenTheConfigurationIs(config)) - .BDDfy(); - } - - [Fact] - public void should_get_null_config() - { - this.Given(_ => GivenFetchFromConsulReturnsNull()) - .When(_ => WhenIGetTheConfiguration()) - .Then(_ => ThenTheConfigurationIsNull()) - .BDDfy(); - } - - [Fact] - public void should_get_config_from_cache() - { - var config = FakeFileConfiguration(); - - this.Given(_ => GivenIHaveAConfiguration(config)) - .And(_ => GivenFetchFromCacheSucceeds()) - .When(_ => WhenIGetTheConfiguration()) - .Then(_ => ThenTheConfigurationIs(config)) - .BDDfy(); - } - - [Fact] - public void should_set_config_key() - { - var config = FakeFileConfiguration(); - - this.Given(_ => GivenIHaveAConfiguration(config)) - .And(_ => GivenTheConfigKeyComesFromFileConfig("Tom")) - .And(_ => GivenFetchFromConsulSucceeds()) - .When(_ => WhenIGetTheConfiguration()) - .And(_ => ThenTheConfigKeyIs("Tom")) - .BDDfy(); - } - - [Fact] - public void should_set_default_config_key() - { - var config = FakeFileConfiguration(); - - this.Given(_ => GivenIHaveAConfiguration(config)) - .And(_ => GivenFetchFromConsulSucceeds()) - .When(_ => WhenIGetTheConfiguration()) - .And(_ => ThenTheConfigKeyIs("InternalConfiguration")) - .BDDfy(); - } - - private void ThenTheConfigKeyIs(string expected) - { - _kvEndpoint - .Verify(x => x.Get(expected, It.IsAny()), Times.Once); - } - - private void GivenTheConfigKeyComesFromFileConfig(string key) - { - _internalRepo - .Setup(x => x.Get()) - .Returns(new OkResponse(new InternalConfiguration(new List(), "", - new ServiceProviderConfigurationBuilder().WithConfigurationKey(key).Build(), "", - new LoadBalancerOptionsBuilder().Build(), "", new QoSOptionsBuilder().Build(), - new HttpHandlerOptionsBuilder().Build()))); - - _repo = new ConsulFileConfigurationRepository(_cache.Object, _internalRepo.Object, _factory.Object, _loggerFactory.Object); - } - - private void ThenTheConfigurationIsNull() - { - _getResult.Data.ShouldBeNull(); - } - - private void ThenTheConfigurationIs(FileConfiguration config) - { - var expected = JsonConvert.SerializeObject(config, Formatting.Indented); - var result = JsonConvert.SerializeObject(_getResult.Data, Formatting.Indented); - result.ShouldBe(expected); - } - - private async Task WhenIGetTheConfiguration() - { - _getResult = await _repo.Get(); - } - - private void GivenWritingToConsulSucceeds() - { - var response = new WriteResult(); - response.Response = true; - - _kvEndpoint - .Setup(x => x.Put(It.IsAny(), It.IsAny())).ReturnsAsync(response); - } - - private void GivenFetchFromCacheSucceeds() - { - _cache.Setup(x => x.Get(It.IsAny(), It.IsAny())).Returns(_fileConfiguration); - } - - private void GivenFetchFromConsulReturnsNull() - { - QueryResult result = new QueryResult(); - - _kvEndpoint - .Setup(x => x.Get(It.IsAny(), It.IsAny())) - .ReturnsAsync(result); - } - - private void GivenFetchFromConsulSucceeds() - { - var json = JsonConvert.SerializeObject(_fileConfiguration, Formatting.Indented); - - var bytes = Encoding.UTF8.GetBytes(json); - - var kvp = new KVPair("OcelotConfiguration"); - kvp.Value = bytes; - - var query = new QueryResult(); - query.Response = kvp; - - _kvEndpoint - .Setup(x => x.Get(It.IsAny(), It.IsAny())) - .ReturnsAsync(query); - } - - private void ThenTheConfigurationIsStoredAs(FileConfiguration config) - { - var json = JsonConvert.SerializeObject(config, Formatting.Indented); - - var bytes = Encoding.UTF8.GetBytes(json); - - _kvEndpoint - .Verify(x => x.Put(It.Is(k => k.Value.SequenceEqual(bytes)), It.IsAny()), Times.Once); - } - - private async Task WhenISetTheConfiguration() - { - _setResult = await _repo.Set(_fileConfiguration); - } - - private void GivenIHaveAConfiguration(FileConfiguration config) - { - _fileConfiguration = config; - } - - private FileConfiguration FakeFileConfiguration() - { - var reRoutes = new List - { - new FileReRoute - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "123.12.12.12", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/asdfs/test/{test}" - } - }; - - var globalConfiguration = new FileGlobalConfiguration - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider - { - Port = 198, - Host = "blah" - } - }; - - return new FileConfiguration - { - GlobalConfiguration = globalConfiguration, - ReRoutes = reRoutes - }; - } - } -} diff --git a/test/Ocelot.UnitTests/Configuration/DiskFileConfigurationRepositoryTests.cs b/test/Ocelot.UnitTests/Configuration/DiskFileConfigurationRepositoryTests.cs index c2e77b38..8f99ef3d 100644 --- a/test/Ocelot.UnitTests/Configuration/DiskFileConfigurationRepositoryTests.cs +++ b/test/Ocelot.UnitTests/Configuration/DiskFileConfigurationRepositoryTests.cs @@ -1,242 +1,287 @@ -namespace Ocelot.UnitTests.Configuration -{ - using System; - using System.Collections.Generic; - using Moq; - using Ocelot.Configuration.File; - using Shouldly; - using TestStack.BDDfy; - using Xunit; - using Newtonsoft.Json; - using System.IO; - using Microsoft.AspNetCore.Hosting; - using Ocelot.Configuration.Repository; - - public class DiskFileConfigurationRepositoryTests - { - private readonly Mock _hostingEnvironment = new Mock(); - private IFileConfigurationRepository _repo; - private string _configurationPath; - private FileConfiguration _result; - private FileConfiguration _fileConfiguration; - - // This is a bit dirty and it is dev.dev so that the ConfigurationBuilderExtensionsTests - // cant pick it up if they run in parralel..sigh these are not really unit - // tests but whatever... - private string _environmentName = "DEV.DEV"; - - public DiskFileConfigurationRepositoryTests() - { - _hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName); - _repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object); - } - - [Fact] - public void should_return_file_configuration() - { - var config = FakeFileConfigurationForGet(); - - this.Given(_ => GivenTheConfigurationIs(config)) - .When(_ => WhenIGetTheReRoutes()) - .Then(_ => ThenTheFollowingIsReturned(config)) - .BDDfy(); - } - - [Fact] - public void should_return_file_configuration_if_environment_name_is_unavailable() - { - var config = FakeFileConfigurationForGet(); - - this.Given(_ => GivenTheEnvironmentNameIsUnavailable()) - .And(_ => GivenTheConfigurationIs(config)) - .When(_ => WhenIGetTheReRoutes()) - .Then(_ => ThenTheFollowingIsReturned(config)) - .BDDfy(); - } - - [Fact] - public void should_set_file_configuration() - { - var config = FakeFileConfigurationForSet(); - - this.Given(_ => GivenIHaveAConfiguration(config)) - .When(_ => WhenISetTheConfiguration()) - .Then(_ => ThenTheConfigurationIsStoredAs(config)) - .And(_ => ThenTheConfigurationJsonIsIndented(config)) - .BDDfy(); - } - - [Fact] - public void should_set_file_configuration_if_environment_name_is_unavailable() - { - var config = FakeFileConfigurationForSet(); - - this.Given(_ => GivenIHaveAConfiguration(config)) - .And(_ => GivenTheEnvironmentNameIsUnavailable()) - .When(_ => WhenISetTheConfiguration()) - .Then(_ => ThenTheConfigurationIsStoredAs(config)) - .And(_ => ThenTheConfigurationJsonIsIndented(config)) - .BDDfy(); - } - - private void GivenTheEnvironmentNameIsUnavailable() - { - _environmentName = null; - _hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName); - _repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object); - } - - private void GivenIHaveAConfiguration(FileConfiguration fileConfiguration) - { - _fileConfiguration = fileConfiguration; - } - - private void WhenISetTheConfiguration() - { - _repo.Set(_fileConfiguration); - _result = _repo.Get().Result.Data; - } - - private void ThenTheConfigurationIsStoredAs(FileConfiguration expecteds) - { - _result.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey); - _result.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host); - _result.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port); - - for(var i = 0; i < _result.ReRoutes.Count; i++) - { - for (int j = 0; j < _result.ReRoutes[i].DownstreamHostAndPorts.Count; j++) - { - var result = _result.ReRoutes[i].DownstreamHostAndPorts[j]; - var expected = expecteds.ReRoutes[i].DownstreamHostAndPorts[j]; - - result.Host.ShouldBe(expected.Host); - result.Port.ShouldBe(expected.Port); - } - - _result.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].DownstreamPathTemplate); - _result.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.ReRoutes[i].DownstreamScheme); - } - } - - private void GivenTheConfigurationIs(FileConfiguration fileConfiguration) - { - _configurationPath = $"{AppContext.BaseDirectory}/ocelot{(string.IsNullOrEmpty(_environmentName) ? string.Empty : ".")}{_environmentName}.json"; - - var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented); - - if (File.Exists(_configurationPath)) - { - File.Delete(_configurationPath); - } - - File.WriteAllText(_configurationPath, jsonConfiguration); - } - - private void ThenTheConfigurationJsonIsIndented(FileConfiguration expecteds) - { - var path = !string.IsNullOrEmpty(_configurationPath) ? _configurationPath : _configurationPath = $"{AppContext.BaseDirectory}/ocelot{(string.IsNullOrEmpty(_environmentName) ? string.Empty : ".")}{_environmentName}.json"; - - var resultText = File.ReadAllText(_configurationPath); - var expectedText = JsonConvert.SerializeObject(expecteds, Formatting.Indented); - resultText.ShouldBe(expectedText); - } - - private void WhenIGetTheReRoutes() - { - _result = _repo.Get().Result.Data; - } - - private void ThenTheFollowingIsReturned(FileConfiguration expecteds) - { - _result.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey); - _result.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host); - _result.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port); - - for(var i = 0; i < _result.ReRoutes.Count; i++) - { - for (int j = 0; j < _result.ReRoutes[i].DownstreamHostAndPorts.Count; j++) - { - var result = _result.ReRoutes[i].DownstreamHostAndPorts[j]; - var expected = expecteds.ReRoutes[i].DownstreamHostAndPorts[j]; - - result.Host.ShouldBe(expected.Host); - result.Port.ShouldBe(expected.Port); - } - - _result.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].DownstreamPathTemplate); - _result.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.ReRoutes[i].DownstreamScheme); - } - } - - private FileConfiguration FakeFileConfigurationForSet() - { - var reRoutes = new List - { - new FileReRoute - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "123.12.12.12", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/asdfs/test/{test}" - } - }; - - var globalConfiguration = new FileGlobalConfiguration - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider - { - Port = 198, - Host = "blah" - } - }; - - return new FileConfiguration - { - GlobalConfiguration = globalConfiguration, - ReRoutes = reRoutes - }; - } - - private FileConfiguration FakeFileConfigurationForGet() - { - var reRoutes = new List - { - new FileReRoute - { - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = 80, - } - }, - DownstreamScheme = "https", - DownstreamPathTemplate = "/test/test/{test}" - } - }; - - var globalConfiguration = new FileGlobalConfiguration - { - ServiceDiscoveryProvider = new FileServiceDiscoveryProvider - { - Port = 198, - Host = "blah" - } - }; - - return new FileConfiguration - { - GlobalConfiguration = globalConfiguration, - ReRoutes = reRoutes - }; - } - } -} +namespace Ocelot.UnitTests.Configuration +{ + using System; + using System.Collections.Generic; + using Moq; + using Ocelot.Configuration.File; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + using Newtonsoft.Json; + using System.IO; + using System.Threading; + using Microsoft.AspNetCore.Hosting; + using Ocelot.Configuration.Repository; + + public class DiskFileConfigurationRepositoryTests : IDisposable + { + private readonly Mock _hostingEnvironment; + private IFileConfigurationRepository _repo; + private string _environmentSpecificPath; + private string _ocelotJsonPath; + private FileConfiguration _result; + private FileConfiguration _fileConfiguration; + + // This is a bit dirty and it is dev.dev so that the ConfigurationBuilderExtensionsTests + // cant pick it up if they run in parralel..and the semaphore stops them running at the same time...sigh + // these are not really unit tests but whatever... + private string _environmentName = "DEV.DEV"; + private static SemaphoreSlim _semaphore; + + public DiskFileConfigurationRepositoryTests() + { + _semaphore = new SemaphoreSlim(1, 1); + _semaphore.Wait(); + _hostingEnvironment = new Mock(); + _hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName); + _repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object); + } + + [Fact] + public void should_return_file_configuration() + { + var config = FakeFileConfigurationForGet(); + + this.Given(_ => GivenTheConfigurationIs(config)) + .When(_ => WhenIGetTheReRoutes()) + .Then(_ => ThenTheFollowingIsReturned(config)) + .BDDfy(); + } + + [Fact] + public void should_return_file_configuration_if_environment_name_is_unavailable() + { + var config = FakeFileConfigurationForGet(); + + this.Given(_ => GivenTheEnvironmentNameIsUnavailable()) + .And(_ => GivenTheConfigurationIs(config)) + .When(_ => WhenIGetTheReRoutes()) + .Then(_ => ThenTheFollowingIsReturned(config)) + .BDDfy(); + } + + [Fact] + public void should_set_file_configuration() + { + var config = FakeFileConfigurationForSet(); + + this.Given(_ => GivenIHaveAConfiguration(config)) + .When(_ => WhenISetTheConfiguration()) + .Then(_ => ThenTheConfigurationIsStoredAs(config)) + .And(_ => ThenTheConfigurationJsonIsIndented(config)) + .BDDfy(); + } + + [Fact] + public void should_set_file_configuration_if_environment_name_is_unavailable() + { + var config = FakeFileConfigurationForSet(); + + this.Given(_ => GivenIHaveAConfiguration(config)) + .And(_ => GivenTheEnvironmentNameIsUnavailable()) + .When(_ => WhenISetTheConfiguration()) + .Then(_ => ThenTheConfigurationIsStoredAs(config)) + .And(_ => ThenTheConfigurationJsonIsIndented(config)) + .BDDfy(); + } + + [Fact] + public void should_set_environment_file_configuration_and_ocelot_file_configuration() + { + var config = FakeFileConfigurationForSet(); + + this.Given(_ => GivenIHaveAConfiguration(config)) + .And(_ => GivenTheConfigurationIs(config)) + .And(_ => GivenTheUserAddedOcelotJson()) + .When(_ => WhenISetTheConfiguration()) + .Then(_ => ThenTheConfigurationIsStoredAs(config)) + .And(_ => ThenTheConfigurationJsonIsIndented(config)) + .Then(_ => ThenTheOcelotJsonIsStoredAs(config)) + .BDDfy(); + } + + private void GivenTheUserAddedOcelotJson() + { + _ocelotJsonPath = $"{AppContext.BaseDirectory}/ocelot.json"; + + if (File.Exists(_ocelotJsonPath)) + { + File.Delete(_ocelotJsonPath); + } + + File.WriteAllText(_ocelotJsonPath, "Doesnt matter"); + } + + private void GivenTheEnvironmentNameIsUnavailable() + { + _environmentName = null; + _hostingEnvironment.Setup(he => he.EnvironmentName).Returns(_environmentName); + _repo = new DiskFileConfigurationRepository(_hostingEnvironment.Object); + } + + private void GivenIHaveAConfiguration(FileConfiguration fileConfiguration) + { + _fileConfiguration = fileConfiguration; + } + + private void WhenISetTheConfiguration() + { + _repo.Set(_fileConfiguration); + _result = _repo.Get().Result.Data; + } + + private void ThenTheConfigurationIsStoredAs(FileConfiguration expecteds) + { + _result.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port); + + for(var i = 0; i < _result.ReRoutes.Count; i++) + { + for (int j = 0; j < _result.ReRoutes[i].DownstreamHostAndPorts.Count; j++) + { + var result = _result.ReRoutes[i].DownstreamHostAndPorts[j]; + var expected = expecteds.ReRoutes[i].DownstreamHostAndPorts[j]; + + result.Host.ShouldBe(expected.Host); + result.Port.ShouldBe(expected.Port); + } + + _result.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].DownstreamPathTemplate); + _result.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.ReRoutes[i].DownstreamScheme); + } + } + + private void ThenTheOcelotJsonIsStoredAs(FileConfiguration expecteds) + { + var resultText = File.ReadAllText(_ocelotJsonPath); + var expectedText = JsonConvert.SerializeObject(expecteds, Formatting.Indented); + resultText.ShouldBe(expectedText); + } + + private void GivenTheConfigurationIs(FileConfiguration fileConfiguration) + { + _environmentSpecificPath = $"{AppContext.BaseDirectory}/ocelot{(string.IsNullOrEmpty(_environmentName) ? string.Empty : ".")}{_environmentName}.json"; + + var jsonConfiguration = JsonConvert.SerializeObject(fileConfiguration, Formatting.Indented); + + if (File.Exists(_environmentSpecificPath)) + { + File.Delete(_environmentSpecificPath); + } + + File.WriteAllText(_environmentSpecificPath, jsonConfiguration); + } + + private void ThenTheConfigurationJsonIsIndented(FileConfiguration expecteds) + { + var path = !string.IsNullOrEmpty(_environmentSpecificPath) ? _environmentSpecificPath : _environmentSpecificPath = $"{AppContext.BaseDirectory}/ocelot{(string.IsNullOrEmpty(_environmentName) ? string.Empty : ".")}{_environmentName}.json"; + + var resultText = File.ReadAllText(path); + var expectedText = JsonConvert.SerializeObject(expecteds, Formatting.Indented); + resultText.ShouldBe(expectedText); + } + + private void WhenIGetTheReRoutes() + { + _result = _repo.Get().Result.Data; + } + + private void ThenTheFollowingIsReturned(FileConfiguration expecteds) + { + _result.GlobalConfiguration.RequestIdKey.ShouldBe(expecteds.GlobalConfiguration.RequestIdKey); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Host.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Host); + _result.GlobalConfiguration.ServiceDiscoveryProvider.Port.ShouldBe(expecteds.GlobalConfiguration.ServiceDiscoveryProvider.Port); + + for(var i = 0; i < _result.ReRoutes.Count; i++) + { + for (int j = 0; j < _result.ReRoutes[i].DownstreamHostAndPorts.Count; j++) + { + var result = _result.ReRoutes[i].DownstreamHostAndPorts[j]; + var expected = expecteds.ReRoutes[i].DownstreamHostAndPorts[j]; + + result.Host.ShouldBe(expected.Host); + result.Port.ShouldBe(expected.Port); + } + + _result.ReRoutes[i].DownstreamPathTemplate.ShouldBe(expecteds.ReRoutes[i].DownstreamPathTemplate); + _result.ReRoutes[i].DownstreamScheme.ShouldBe(expecteds.ReRoutes[i].DownstreamScheme); + } + } + + private FileConfiguration FakeFileConfigurationForSet() + { + var reRoutes = new List + { + new FileReRoute + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "123.12.12.12", + Port = 80, + } + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/asdfs/test/{test}" + } + }; + + var globalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Port = 198, + Host = "blah" + } + }; + + return new FileConfiguration + { + GlobalConfiguration = globalConfiguration, + ReRoutes = reRoutes + }; + } + + private FileConfiguration FakeFileConfigurationForGet() + { + var reRoutes = new List + { + new FileReRoute + { + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = 80, + } + }, + DownstreamScheme = "https", + DownstreamPathTemplate = "/test/test/{test}" + } + }; + + var globalConfiguration = new FileGlobalConfiguration + { + ServiceDiscoveryProvider = new FileServiceDiscoveryProvider + { + Port = 198, + Host = "blah" + } + }; + + return new FileConfiguration + { + GlobalConfiguration = globalConfiguration, + ReRoutes = reRoutes + }; + } + + public void Dispose() + { + _semaphore.Release(); + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs b/test/Ocelot.UnitTests/Configuration/FileConfigurationPollerTests.cs similarity index 67% rename from test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs rename to test/Ocelot.UnitTests/Configuration/FileConfigurationPollerTests.cs index 17fc604d..4c6a6016 100644 --- a/test/Ocelot.UnitTests/Configuration/ConsulFileConfigurationPollerTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileConfigurationPollerTests.cs @@ -12,37 +12,39 @@ using TestStack.BDDfy; using Xunit; using Shouldly; using static Ocelot.Infrastructure.Wait; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration; namespace Ocelot.UnitTests.Configuration { - public class ConsulFileConfigurationPollerTests : IDisposable + public class FileConfigurationPollerTests : IDisposable { - private readonly ConsulFileConfigurationPoller _poller; + private readonly FileConfigurationPoller _poller; private Mock _factory; private readonly Mock _repo; - private readonly Mock _setter; private readonly FileConfiguration _fileConfig; - private Mock _config; + private Mock _config; + private readonly Mock _internalConfigRepo; + private readonly Mock _internalConfigCreator; + private IInternalConfiguration _internalConfig; - public ConsulFileConfigurationPollerTests() + public FileConfigurationPollerTests() { var logger = new Mock(); _factory = new Mock(); - _factory.Setup(x => x.CreateLogger()).Returns(logger.Object); + _factory.Setup(x => x.CreateLogger()).Returns(logger.Object); _repo = new Mock(); - _setter = new Mock(); _fileConfig = new FileConfiguration(); - _config = new Mock(); + _config = new Mock(); _repo.Setup(x => x.Get()).ReturnsAsync(new OkResponse(_fileConfig)); _config.Setup(x => x.Delay).Returns(100); - _poller = new ConsulFileConfigurationPoller(_factory.Object, _repo.Object, _setter.Object, _config.Object); + _internalConfigRepo = new Mock(); + _internalConfigCreator = new Mock(); + _internalConfigCreator.Setup(x => x.Create(It.IsAny())).ReturnsAsync(new OkResponse(_internalConfig)); + _poller = new FileConfigurationPoller(_factory.Object, _repo.Object, _config.Object, _internalConfigRepo.Object, _internalConfigCreator.Object); + _poller.StartAsync(new CancellationToken()); } - - public void Dispose() - { - _poller.Dispose(); - } - + [Fact] public void should_start() { @@ -69,7 +71,7 @@ namespace Ocelot.UnitTests.Configuration } }; - this.Given(x => WhenTheConfigIsChangedInConsul(newConfig, 0)) + this.Given(x => WhenTheConfigIsChanged(newConfig, 0)) .Then(x => ThenTheSetterIsCalledAtLeast(newConfig, 1)) .BDDfy(); } @@ -94,13 +96,13 @@ namespace Ocelot.UnitTests.Configuration } }; - this.Given(x => WhenTheConfigIsChangedInConsul(newConfig, 10)) + this.Given(x => WhenTheConfigIsChanged(newConfig, 10)) .Then(x => ThenTheSetterIsCalled(newConfig, 1)) .BDDfy(); } [Fact] - public void should_do_nothing_if_call_to_consul_fails() + public void should_do_nothing_if_call_to_provider_fails() { var newConfig = new FileConfiguration { @@ -119,19 +121,19 @@ namespace Ocelot.UnitTests.Configuration } }; - this.Given(x => WhenConsulErrors()) + this.Given(x => WhenProviderErrors()) .Then(x => ThenTheSetterIsCalled(newConfig, 0)) .BDDfy(); } - private void WhenConsulErrors() + private void WhenProviderErrors() { _repo .Setup(x => x.Get()) .ReturnsAsync(new ErrorResponse(new AnyError())); } - private void WhenTheConfigIsChangedInConsul(FileConfiguration newConfig, int delay) + private void WhenTheConfigIsChanged(FileConfiguration newConfig, int delay) { _repo .Setup(x => x.Get()) @@ -141,10 +143,11 @@ namespace Ocelot.UnitTests.Configuration private void ThenTheSetterIsCalled(FileConfiguration fileConfig, int times) { - var result = WaitFor(2000).Until(() => { + var result = WaitFor(4000).Until(() => { try { - _setter.Verify(x => x.Set(fileConfig), Times.Exactly(times)); + _internalConfigRepo.Verify(x => x.AddOrReplace(_internalConfig), Times.Exactly(times)); + _internalConfigCreator.Verify(x => x.Create(fileConfig), Times.Exactly(times)); return true; } catch(Exception) @@ -157,10 +160,11 @@ namespace Ocelot.UnitTests.Configuration private void ThenTheSetterIsCalledAtLeast(FileConfiguration fileConfig, int times) { - var result = WaitFor(2000).Until(() => { + var result = WaitFor(4000).Until(() => { try { - _setter.Verify(x => x.Set(fileConfig), Times.AtLeast(times)); + _internalConfigRepo.Verify(x => x.AddOrReplace(_internalConfig), Times.AtLeast(times)); + _internalConfigCreator.Verify(x => x.Create(fileConfig), Times.AtLeast(times)); return true; } catch(Exception) @@ -170,5 +174,10 @@ namespace Ocelot.UnitTests.Configuration }); result.ShouldBeTrue(); } + + public void Dispose() + { + _poller.Dispose(); + } } } diff --git a/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs index af7dfed7..f545e04c 100644 --- a/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/FileInternalConfigurationCreatorTests.cs @@ -17,6 +17,7 @@ using Ocelot.Errors; using Ocelot.UnitTests.TestData; using Ocelot.Values; + using System; public class FileInternalConfigurationCreatorTests { @@ -35,7 +36,7 @@ private readonly Mock _rateLimitOptions; private readonly Mock _regionCreator; private readonly Mock _httpHandlerOptionsCreator; - private readonly Mock _adminPath; + private readonly Mock _serviceProvider; private readonly Mock _headerFindAndReplaceCreator; private readonly Mock _downstreamAddressesCreator; @@ -53,7 +54,7 @@ _rateLimitOptions = new Mock(); _regionCreator = new Mock(); _httpHandlerOptionsCreator = new Mock(); - _adminPath = new Mock(); + _serviceProvider = new Mock(); _headerFindAndReplaceCreator = new Mock(); _downstreamAddressesCreator = new Mock(); @@ -70,7 +71,7 @@ _rateLimitOptions.Object, _regionCreator.Object, _httpHandlerOptionsCreator.Object, - _adminPath.Object, + _serviceProvider.Object, _headerFindAndReplaceCreator.Object, _downstreamAddressesCreator.Object); } @@ -821,6 +822,54 @@ .BDDfy(); } + [Fact] + public void should_set_up_dynamic_re_routes() + { + var reRouteOptions = new ReRouteOptionsBuilder() + .Build(); + + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithEnableRateLimiting(true) + .WithRateLimitOptions(new RateLimitOptionsBuilder().Build()) + .Build(); + + var rateLimitOptions = new RateLimitOptionsBuilder() + .WithRateLimitRule(new RateLimitRule("1s", 1, 1)) + .Build(); + + this.Given(x => x.GivenTheConfigIs(new FileConfiguration + { + DynamicReRoutes = new List + { + new FileDynamicReRoute + { + ServiceName = "test", + RateLimitRule = new FileRateLimitRule + { + Period = "1s", + PeriodTimespan = 1, + Limit = 1 + } + } + }, + })) + .And(x => x.GivenTheConfigIsValid()) + .And(x => GivenTheRateLimitCreatorReturns(rateLimitOptions)) + .And(x => GivenTheDownstreamAddresses()) + .And(x => GivenTheHeaderFindAndReplaceCreatorReturns()) + .And(x => x.GivenTheFollowingOptionsAreReturned(reRouteOptions)) + .When(x => x.WhenICreateTheConfig()) + .Then(x => x.ThenTheDynamicReRouteIsSetUp("test", rateLimitOptions.RateLimitRule)) + .BDDfy(); + } + + private void GivenTheRateLimitCreatorReturns(RateLimitOptions rateLimitOptions) + { + _rateLimitOptions + .Setup(x => x.Create(It.IsAny(), It.IsAny())) + .Returns(rateLimitOptions); + } + private void GivenTheConfigIsInvalid(List errors) { _validator @@ -844,7 +893,7 @@ private void ThenTheRateLimitOptionsCreatorIsCalledCorrectly() { _rateLimitOptions - .Verify(x => x.Create(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + .Verify(x => x.Create(It.IsAny(), It.IsAny()), Times.Once); } private void GivenTheConfigIsValid() @@ -864,6 +913,16 @@ _config = _internalConfigurationCreator.Create(_fileConfiguration).Result; } + private void ThenTheDynamicReRouteIsSetUp(string serviceName, RateLimitRule rateLimitOptions) + { + var dynamic = _config.Data.ReRoutes[0].DownstreamReRoute[0]; + dynamic.ServiceName.ShouldBe(serviceName); + dynamic.EnableEndpointEndpointRateLimiting.ShouldBeTrue(); + dynamic.RateLimitOptions.RateLimitRule.Period.ShouldBe(rateLimitOptions.Period); + dynamic.RateLimitOptions.RateLimitRule.Limit.ShouldBe(rateLimitOptions.Limit); + dynamic.RateLimitOptions.RateLimitRule.PeriodTimespan.ShouldBe(rateLimitOptions.PeriodTimespan); + } + private void ThenTheReRoutesAre(List expectedReRoutes) { for (int i = 0; i < _config.Data.ReRoutes.Count; i++) diff --git a/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs index 94b5413b..e20aca02 100644 --- a/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/HttpHandlerOptionsCreatorTests.cs @@ -1,175 +1,182 @@ -using System; -using Butterfly.Client.Tracing; -using Butterfly.OpenTracing; -using Ocelot.Configuration; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.File; -using Ocelot.Requester; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class HttpHandlerOptionsCreatorTests - { - private IHttpHandlerOptionsCreator _httpHandlerOptionsCreator; - private FileReRoute _fileReRoute; - private HttpHandlerOptions _httpHandlerOptions; - private IServiceTracer _serviceTracer; - - public HttpHandlerOptionsCreatorTests() - { - _serviceTracer = new FakeServiceTracer(); - _httpHandlerOptionsCreator = new HttpHandlerOptionsCreator(_serviceTracer); - } - - [Fact] - public void should_not_use_tracing_if_fake_tracer_registered() - { - var fileReRoute = new FileReRoute - { - HttpHandlerOptions = new FileHttpHandlerOptions - { - UseTracing = true - } - }; - - var expectedOptions = new HttpHandlerOptions(false, false, false, true); - - this.Given(x => GivenTheFollowing(fileReRoute)) - .When(x => WhenICreateHttpHandlerOptions()) - .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) - .BDDfy(); - } - - [Fact] - public void should_use_tracing_if_real_tracer_registered() - { - var fileReRoute = new FileReRoute - { - HttpHandlerOptions = new FileHttpHandlerOptions - { - UseTracing = true - } - }; - - var expectedOptions = new HttpHandlerOptions(false, false, true, true); - - this.Given(x => GivenTheFollowing(fileReRoute)) - .And(x => GivenARealTracer()) - .When(x => WhenICreateHttpHandlerOptions()) - .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) - .BDDfy(); - } - - [Fact] - public void should_create_options_with_useCookie_false_and_allowAutoRedirect_true_as_default() - { - var fileReRoute = new FileReRoute(); - var expectedOptions = new HttpHandlerOptions(false, false, false, true); - - this.Given(x => GivenTheFollowing(fileReRoute)) - .When(x => WhenICreateHttpHandlerOptions()) - .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) - .BDDfy(); - } - - [Fact] - public void should_create_options_with_specified_useCookie_and_allowAutoRedirect() - { - var fileReRoute = new FileReRoute - { - HttpHandlerOptions = new FileHttpHandlerOptions - { - AllowAutoRedirect = false, - UseCookieContainer = false, - UseTracing = false - } - }; - - var expectedOptions = new HttpHandlerOptions(false, false, false, true); - - this.Given(x => GivenTheFollowing(fileReRoute)) - .When(x => WhenICreateHttpHandlerOptions()) - .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) - .BDDfy(); +using System; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Configuration; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Ocelot.Requester; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Ocelot.Logging; + + public class HttpHandlerOptionsCreatorTests + { + private IHttpHandlerOptionsCreator _httpHandlerOptionsCreator; + private FileReRoute _fileReRoute; + private HttpHandlerOptions _httpHandlerOptions; + private IServiceProvider _serviceProvider; + private IServiceCollection _serviceCollection; + + public HttpHandlerOptionsCreatorTests() + { + _serviceCollection = new ServiceCollection(); + _serviceProvider = _serviceCollection.BuildServiceProvider(); + _httpHandlerOptionsCreator = new HttpHandlerOptionsCreator(_serviceProvider); } - [Fact] - public void should_create_options_with_useproxy_true_as_default() - { - var fileReRoute = new FileReRoute - { - HttpHandlerOptions = new FileHttpHandlerOptions() - }; - - var expectedOptions = new HttpHandlerOptions(false, false, false, true); - - this.Given(x => GivenTheFollowing(fileReRoute)) - .When(x => WhenICreateHttpHandlerOptions()) - .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) - .BDDfy(); + [Fact] + public void should_not_use_tracing_if_fake_tracer_registered() + { + var fileReRoute = new FileReRoute + { + HttpHandlerOptions = new FileHttpHandlerOptions + { + UseTracing = true + } + }; + + var expectedOptions = new HttpHandlerOptions(false, false, false, true); + + this.Given(x => GivenTheFollowing(fileReRoute)) + .When(x => WhenICreateHttpHandlerOptions()) + .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) + .BDDfy(); } - [Fact] - public void should_create_options_with_specified_useproxy() - { - var fileReRoute = new FileReRoute - { - HttpHandlerOptions = new FileHttpHandlerOptions - { - UseProxy = false - } - }; - - var expectedOptions = new HttpHandlerOptions(false, false, false, false); - - this.Given(x => GivenTheFollowing(fileReRoute)) - .When(x => WhenICreateHttpHandlerOptions()) - .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) - .BDDfy(); - } - - private void GivenTheFollowing(FileReRoute fileReRoute) - { - _fileReRoute = fileReRoute; - } - - private void WhenICreateHttpHandlerOptions() - { - _httpHandlerOptions = _httpHandlerOptionsCreator.Create(_fileReRoute.HttpHandlerOptions); - } - - private void ThenTheFollowingOptionsReturned(HttpHandlerOptions expected) - { - _httpHandlerOptions.ShouldNotBeNull(); - _httpHandlerOptions.AllowAutoRedirect.ShouldBe(expected.AllowAutoRedirect); - _httpHandlerOptions.UseCookieContainer.ShouldBe(expected.UseCookieContainer); + [Fact] + public void should_use_tracing_if_real_tracer_registered() + { + var fileReRoute = new FileReRoute + { + HttpHandlerOptions = new FileHttpHandlerOptions + { + UseTracing = true + } + }; + + var expectedOptions = new HttpHandlerOptions(false, false, true, true); + + this.Given(x => GivenTheFollowing(fileReRoute)) + .And(x => GivenARealTracer()) + .When(x => WhenICreateHttpHandlerOptions()) + .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) + .BDDfy(); + } + + [Fact] + public void should_create_options_with_useCookie_false_and_allowAutoRedirect_true_as_default() + { + var fileReRoute = new FileReRoute(); + var expectedOptions = new HttpHandlerOptions(false, false, false, true); + + this.Given(x => GivenTheFollowing(fileReRoute)) + .When(x => WhenICreateHttpHandlerOptions()) + .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) + .BDDfy(); + } + + [Fact] + public void should_create_options_with_specified_useCookie_and_allowAutoRedirect() + { + var fileReRoute = new FileReRoute + { + HttpHandlerOptions = new FileHttpHandlerOptions + { + AllowAutoRedirect = false, + UseCookieContainer = false, + UseTracing = false + } + }; + + var expectedOptions = new HttpHandlerOptions(false, false, false, true); + + this.Given(x => GivenTheFollowing(fileReRoute)) + .When(x => WhenICreateHttpHandlerOptions()) + .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) + .BDDfy(); + } + + [Fact] + public void should_create_options_with_useproxy_true_as_default() + { + var fileReRoute = new FileReRoute + { + HttpHandlerOptions = new FileHttpHandlerOptions() + }; + + var expectedOptions = new HttpHandlerOptions(false, false, false, true); + + this.Given(x => GivenTheFollowing(fileReRoute)) + .When(x => WhenICreateHttpHandlerOptions()) + .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) + .BDDfy(); + } + + [Fact] + public void should_create_options_with_specified_useproxy() + { + var fileReRoute = new FileReRoute + { + HttpHandlerOptions = new FileHttpHandlerOptions + { + UseProxy = false + } + }; + + var expectedOptions = new HttpHandlerOptions(false, false, false, false); + + this.Given(x => GivenTheFollowing(fileReRoute)) + .When(x => WhenICreateHttpHandlerOptions()) + .Then(x => ThenTheFollowingOptionsReturned(expectedOptions)) + .BDDfy(); + } + + private void GivenTheFollowing(FileReRoute fileReRoute) + { + _fileReRoute = fileReRoute; + } + + private void WhenICreateHttpHandlerOptions() + { + _httpHandlerOptions = _httpHandlerOptionsCreator.Create(_fileReRoute.HttpHandlerOptions); + } + + private void ThenTheFollowingOptionsReturned(HttpHandlerOptions expected) + { + _httpHandlerOptions.ShouldNotBeNull(); + _httpHandlerOptions.AllowAutoRedirect.ShouldBe(expected.AllowAutoRedirect); + _httpHandlerOptions.UseCookieContainer.ShouldBe(expected.UseCookieContainer); _httpHandlerOptions.UseTracing.ShouldBe(expected.UseTracing); - _httpHandlerOptions.UseProxy.ShouldBe(expected.UseProxy); - } - - private void GivenARealTracer() - { - var tracer = new RealTracer(); - _httpHandlerOptionsCreator = new HttpHandlerOptionsCreator(tracer); - } - - class RealTracer : IServiceTracer - { - public ITracer Tracer => throw new NotImplementedException(); - - public string ServiceName => throw new NotImplementedException(); - - public string Environment => throw new NotImplementedException(); - - public string Identity => throw new NotImplementedException(); - - public ISpan Start(ISpanBuilder spanBuilder) - { - throw new NotImplementedException(); - } - } - } -} + _httpHandlerOptions.UseProxy.ShouldBe(expected.UseProxy); + } + + private void GivenARealTracer() + { + var tracer = new FakeTracer(); + _serviceCollection.AddSingleton(); + _serviceProvider = _serviceCollection.BuildServiceProvider(); + _httpHandlerOptionsCreator = new HttpHandlerOptionsCreator(_serviceProvider); + } + + class FakeTracer : ITracer + { + public void Event(HttpContext httpContext, string @event) + { + throw new NotImplementedException(); + } + + public Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken, Action addTraceIdToRepo, + Func> baseSendAsync) + { + throw new NotImplementedException(); + } + } + } +} diff --git a/test/Ocelot.UnitTests/Configuration/IdentityServerConfigurationCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/IdentityServerConfigurationCreatorTests.cs deleted file mode 100644 index 5f8d3210..00000000 --- a/test/Ocelot.UnitTests/Configuration/IdentityServerConfigurationCreatorTests.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Ocelot.Configuration.Creator; -using Shouldly; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class IdentityServerConfigurationCreatorTests - { - [Fact] - public void happy_path_only_exists_for_test_coverage_even_uncle_bob_probably_wouldnt_test_this() - { - var result = IdentityServerConfigurationCreator.GetIdentityServerConfiguration("secret"); - result.ApiName.ShouldBe("admin"); - } - } -} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs b/test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs index 69093cca..e77e3a62 100644 --- a/test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs +++ b/test/Ocelot.UnitTests/Configuration/RateLimitOptionsCreatorTests.cs @@ -1,108 +1,108 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Configuration.Creator; -using Ocelot.Configuration.File; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Configuration -{ - public class RateLimitOptionsCreatorTests - { - private FileReRoute _fileReRoute; - private FileGlobalConfiguration _fileGlobalConfig; - private bool _enabled; - private RateLimitOptionsCreator _creator; - private RateLimitOptions _result; - - public RateLimitOptionsCreatorTests() - { - _creator = new RateLimitOptionsCreator(); - } - - [Fact] - public void should_create_rate_limit_options() - { - var fileReRoute = new FileReRoute - { - RateLimitOptions = new FileRateLimitRule - { - ClientWhitelist = new List(), - Period = "Period", - Limit = 1, - PeriodTimespan = 1, - EnableRateLimiting = true - } - }; - var fileGlobalConfig = new FileGlobalConfiguration - { - RateLimitOptions = new FileRateLimitOptions - { - ClientIdHeader = "ClientIdHeader", - DisableRateLimitHeaders = true, - QuotaExceededMessage = "QuotaExceededMessage", - RateLimitCounterPrefix = "RateLimitCounterPrefix", - HttpStatusCode = 200 - } - }; - var expected = new RateLimitOptionsBuilder() - .WithClientIdHeader("ClientIdHeader") - .WithClientWhiteList(fileReRoute.RateLimitOptions.ClientWhitelist) - .WithDisableRateLimitHeaders(true) - .WithEnableRateLimiting(true) - .WithHttpStatusCode(200) - .WithQuotaExceededMessage("QuotaExceededMessage") - .WithRateLimitCounterPrefix("RateLimitCounterPrefix") - .WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period, - fileReRoute.RateLimitOptions.PeriodTimespan, - fileReRoute.RateLimitOptions.Limit)) - .Build(); - - this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) - .And(x => x.GivenTheFollowingFileGlobalConfig(fileGlobalConfig)) - .And(x => x.GivenRateLimitingIsEnabled()) - .When(x => x.WhenICreate()) - .Then(x => x.ThenTheFollowingIsReturned(expected)) - .BDDfy(); - } - - private void GivenTheFollowingFileReRoute(FileReRoute fileReRoute) - { - _fileReRoute = fileReRoute; - } - - private void GivenTheFollowingFileGlobalConfig(FileGlobalConfiguration fileGlobalConfig) - { - _fileGlobalConfig = fileGlobalConfig; - } - - private void GivenRateLimitingIsEnabled() - { - _enabled = true; - } - - private void WhenICreate() - { - _result = _creator.Create(_fileReRoute, _fileGlobalConfig, _enabled); - } - - private void ThenTheFollowingIsReturned(RateLimitOptions expected) - { - _result.ClientIdHeader.ShouldBe(expected.ClientIdHeader); - _result.ClientWhitelist.ShouldBe(expected.ClientWhitelist); - _result.DisableRateLimitHeaders.ShouldBe(expected.DisableRateLimitHeaders); - _result.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting); - _result.HttpStatusCode.ShouldBe(expected.HttpStatusCode); - _result.QuotaExceededMessage.ShouldBe(expected.QuotaExceededMessage); - _result.RateLimitCounterPrefix.ShouldBe(expected.RateLimitCounterPrefix); - _result.RateLimitRule.Limit.ShouldBe(expected.RateLimitRule.Limit); - _result.RateLimitRule.Period.ShouldBe(expected.RateLimitRule.Period); - TimeSpan.FromSeconds(_result.RateLimitRule.PeriodTimespan).Ticks.ShouldBe(TimeSpan.FromSeconds(expected.RateLimitRule.PeriodTimespan).Ticks); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Configuration.Creator; +using Ocelot.Configuration.File; +using Shouldly; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Configuration +{ + public class RateLimitOptionsCreatorTests + { + private FileReRoute _fileReRoute; + private FileGlobalConfiguration _fileGlobalConfig; + private bool _enabled; + private RateLimitOptionsCreator _creator; + private RateLimitOptions _result; + + public RateLimitOptionsCreatorTests() + { + _creator = new RateLimitOptionsCreator(); + } + + [Fact] + public void should_create_rate_limit_options() + { + var fileReRoute = new FileReRoute + { + RateLimitOptions = new FileRateLimitRule + { + ClientWhitelist = new List(), + Period = "Period", + Limit = 1, + PeriodTimespan = 1, + EnableRateLimiting = true + } + }; + var fileGlobalConfig = new FileGlobalConfiguration + { + RateLimitOptions = new FileRateLimitOptions + { + ClientIdHeader = "ClientIdHeader", + DisableRateLimitHeaders = true, + QuotaExceededMessage = "QuotaExceededMessage", + RateLimitCounterPrefix = "RateLimitCounterPrefix", + HttpStatusCode = 200 + } + }; + var expected = new RateLimitOptionsBuilder() + .WithClientIdHeader("ClientIdHeader") + .WithClientWhiteList(fileReRoute.RateLimitOptions.ClientWhitelist) + .WithDisableRateLimitHeaders(true) + .WithEnableRateLimiting(true) + .WithHttpStatusCode(200) + .WithQuotaExceededMessage("QuotaExceededMessage") + .WithRateLimitCounterPrefix("RateLimitCounterPrefix") + .WithRateLimitRule(new RateLimitRule(fileReRoute.RateLimitOptions.Period, + fileReRoute.RateLimitOptions.PeriodTimespan, + fileReRoute.RateLimitOptions.Limit)) + .Build(); + + this.Given(x => x.GivenTheFollowingFileReRoute(fileReRoute)) + .And(x => x.GivenTheFollowingFileGlobalConfig(fileGlobalConfig)) + .And(x => x.GivenRateLimitingIsEnabled()) + .When(x => x.WhenICreate()) + .Then(x => x.ThenTheFollowingIsReturned(expected)) + .BDDfy(); + } + + private void GivenTheFollowingFileReRoute(FileReRoute fileReRoute) + { + _fileReRoute = fileReRoute; + } + + private void GivenTheFollowingFileGlobalConfig(FileGlobalConfiguration fileGlobalConfig) + { + _fileGlobalConfig = fileGlobalConfig; + } + + private void GivenRateLimitingIsEnabled() + { + _enabled = true; + } + + private void WhenICreate() + { + _result = _creator.Create(_fileReRoute.RateLimitOptions, _fileGlobalConfig); + } + + private void ThenTheFollowingIsReturned(RateLimitOptions expected) + { + _result.ClientIdHeader.ShouldBe(expected.ClientIdHeader); + _result.ClientWhitelist.ShouldBe(expected.ClientWhitelist); + _result.DisableRateLimitHeaders.ShouldBe(expected.DisableRateLimitHeaders); + _result.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting); + _result.HttpStatusCode.ShouldBe(expected.HttpStatusCode); + _result.QuotaExceededMessage.ShouldBe(expected.QuotaExceededMessage); + _result.RateLimitCounterPrefix.ShouldBe(expected.RateLimitCounterPrefix); + _result.RateLimitRule.Limit.ShouldBe(expected.RateLimitRule.Limit); + _result.RateLimitRule.Period.ShouldBe(expected.RateLimitRule.Period); + TimeSpan.FromSeconds(_result.RateLimitRule.PeriodTimespan).Ticks.ShouldBe(TimeSpan.FromSeconds(expected.RateLimitRule.PeriodTimespan).Ticks); + } + } +} diff --git a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs index 8432d584..9a42dba9 100644 --- a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs +++ b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs @@ -8,14 +8,11 @@ using Ocelot.Responses; using TestStack.BDDfy; using Xunit; using Shouldly; -using Ocelot.Raft; -using Rafty.Concensus; using Ocelot.Configuration; namespace Ocelot.UnitTests.Controllers { using Ocelot.Configuration.Repository; - using Rafty.Concensus.Node; public class FileConfigurationControllerTests { @@ -25,7 +22,6 @@ namespace Ocelot.UnitTests.Controllers private IActionResult _result; private FileConfiguration _fileConfiguration; private readonly Mock _provider; - private Mock _node; public FileConfigurationControllerTests() { @@ -70,33 +66,6 @@ namespace Ocelot.UnitTests.Controllers .BDDfy(); } - [Fact] - public void should_post_file_configuration_using_raft_node() - { - var expected = new FileConfiguration(); - - this.Given(x => GivenTheFileConfiguration(expected)) - .And(x => GivenARaftNodeIsRegistered()) - .And(x => GivenTheNodeReturnsOK()) - .And(x => GivenTheConfigSetterReturns(new OkResponse())) - .When(x => WhenIPostTheFileConfiguration()) - .Then(x => x.ThenTheNodeIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_return_error_when_cannot_set_config_using_raft_node() - { - var expected = new FileConfiguration(); - - this.Given(x => GivenTheFileConfiguration(expected)) - .And(x => GivenARaftNodeIsRegistered()) - .And(x => GivenTheNodeReturnsError()) - .When(x => WhenIPostTheFileConfiguration()) - .Then(x => ThenTheResponseIs()) - .BDDfy(); - } - [Fact] public void should_return_error_when_cannot_set_config() { @@ -110,33 +79,6 @@ namespace Ocelot.UnitTests.Controllers .BDDfy(); } - private void ThenTheNodeIsCalledCorrectly() - { - _node.Verify(x => x.Accept(It.IsAny()), Times.Once); - } - - private void GivenARaftNodeIsRegistered() - { - _node = new Mock(); - _provider - .Setup(x => x.GetService(typeof(INode))) - .Returns(_node.Object); - } - - private void GivenTheNodeReturnsOK() - { - _node - .Setup(x => x.Accept(It.IsAny())) - .ReturnsAsync(new Rafty.Infrastructure.OkResponse(new UpdateFileConfiguration(new FileConfiguration()))); - } - - private void GivenTheNodeReturnsError() - { - _node - .Setup(x => x.Accept(It.IsAny())) - .ReturnsAsync(new Rafty.Infrastructure.ErrorResponse("error", new UpdateFileConfiguration(new FileConfiguration()))); - } - private void GivenTheConfigSetterReturns(Response response) { _setter diff --git a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs index 630b069c..da2bc341 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/ConfigurationBuilderExtensionsTests.cs @@ -46,7 +46,7 @@ [Fact] public void should_merge_files() { - this.Given(_ => GivenMultipleConfigurationFiles(false)) + this.Given(_ => GivenMultipleConfigurationFiles("", false)) .When(_ => WhenIAddOcelotConfiguration(false)) .Then(_ => ThenTheConfigsAreMerged()) .BDDfy(); @@ -55,15 +55,30 @@ [Fact] public void should_merge_files_except_env() { - this.Given(_ => GivenMultipleConfigurationFiles(true)) + this.Given(_ => GivenMultipleConfigurationFiles("", true)) .When(_ => WhenIAddOcelotConfiguration(true)) .Then(_ => ThenTheConfigsAreMerged()) .And(_ => NotContainsEnvSpecificConfig()) .BDDfy(); } - private void GivenMultipleConfigurationFiles(bool addEnvSpecificConfig) + [Fact] + public void should_merge_files_in_specific_folder() { + string configFolder = "ConfigFiles"; + this.Given(_ => GivenMultipleConfigurationFiles(configFolder, false)) + .When(_ => WhenIAddOcelotConfigurationWithSpecificFolder(configFolder)) + .Then(_ => ThenTheConfigsAreMerged()) + .BDDfy(); + } + + private void GivenMultipleConfigurationFiles(string folder, bool addEnvSpecificConfig) + { + if (!string.IsNullOrEmpty(folder)) + { + Directory.CreateDirectory(folder); + } + _globalConfig = new FileConfiguration { GlobalConfiguration = new FileGlobalConfiguration @@ -209,14 +224,20 @@ } }; - File.WriteAllText("ocelot.global.json", JsonConvert.SerializeObject(_globalConfig)); - File.WriteAllText("ocelot.reRoutesA.json", JsonConvert.SerializeObject(_reRouteA)); - File.WriteAllText("ocelot.reRoutesB.json", JsonConvert.SerializeObject(_reRouteB)); - File.WriteAllText("ocelot.aggregates.json", JsonConvert.SerializeObject(_aggregate)); + string globalFilename = Path.Combine(folder, "ocelot.global.json"); + string reroutesAFilename = Path.Combine(folder, "ocelot.reRoutesA.json"); + string reroutesBFilename = Path.Combine(folder, "ocelot.reRoutesB.json"); + string aggregatesFilename = Path.Combine(folder, "ocelot.aggregates.json"); + + File.WriteAllText(globalFilename, JsonConvert.SerializeObject(_globalConfig)); + File.WriteAllText(reroutesAFilename, JsonConvert.SerializeObject(_reRouteA)); + File.WriteAllText(reroutesBFilename, JsonConvert.SerializeObject(_reRouteB)); + File.WriteAllText(aggregatesFilename, JsonConvert.SerializeObject(_aggregate)); if (addEnvSpecificConfig) { - File.WriteAllText("ocelot.Env.json", JsonConvert.SerializeObject(_envSpecific)); + string envSpecificFilename = Path.Combine(folder, "ocelot.Env.json"); + File.WriteAllText(envSpecificFilename, JsonConvert.SerializeObject(_envSpecific)); } } @@ -232,6 +253,13 @@ _configRoot = builder.Build(); } + private void WhenIAddOcelotConfigurationWithSpecificFolder(string folder) + { + IConfigurationBuilder builder = new ConfigurationBuilder(); + builder.AddOcelot(folder); + _configRoot = builder.Build(); + } + private void ThenTheConfigsAreMerged() { var fc = (FileConfiguration)_configRoot.Get(typeof(FileConfiguration)); diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index 358dab18..6459daf9 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -1,28 +1,23 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using CacheManager.Core; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Hosting.Internal; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Ocelot.Cache; -using Ocelot.Configuration; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Setter; -using Ocelot.DependencyInjection; -using Ocelot.Requester; -using Ocelot.UnitTests.Requester; -using Shouldly; -using IdentityServer4.AccessTokenValidation; -using TestStack.BDDfy; -using Xunit; -using static Ocelot.UnitTests.Middleware.UserDefinedResponseAggregatorTests; -using Ocelot.Middleware.Multiplexer; - namespace Ocelot.UnitTests.DependencyInjection { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.Hosting.Internal; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Ocelot.Configuration.Setter; + using Ocelot.DependencyInjection; + using Ocelot.Requester; + using Ocelot.UnitTests.Requester; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + using static Ocelot.UnitTests.Middleware.UserDefinedResponseAggregatorTests; + using Ocelot.Middleware.Multiplexer; + public class OcelotBuilderTests { private readonly IServiceCollection _services; @@ -79,57 +74,6 @@ namespace Ocelot.UnitTests.DependencyInjection .BDDfy(); } - [Fact] - public void should_set_up_cache_manager() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => WhenISetUpCacheManager()) - .Then(x => ThenAnExceptionIsntThrown()) - .And(x => OnlyOneVersionOfEachCacheIsRegistered()) - .BDDfy(); - } - - [Fact] - public void should_set_up_consul() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => WhenISetUpConsul()) - .Then(x => ThenAnExceptionIsntThrown()) - .BDDfy(); - } - - [Fact] - public void should_set_up_rafty() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => WhenISetUpRafty()) - .Then(x => ThenAnExceptionIsntThrown()) - .Then(x => ThenTheCorrectAdminPathIsRegitered()) - .BDDfy(); - } - - [Fact] - public void should_set_up_administration_with_identity_server_options() - { - Action options = o => {}; - - this.Given(x => WhenISetUpOcelotServices()) - .When(x => WhenISetUpAdministration(options)) - .Then(x => ThenAnExceptionIsntThrown()) - .Then(x => ThenTheCorrectAdminPathIsRegitered()) - .BDDfy(); - } - - [Fact] - public void should_set_up_administration() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => WhenISetUpAdministration()) - .Then(x => ThenAnExceptionIsntThrown()) - .Then(x => ThenTheCorrectAdminPathIsRegitered()) - .BDDfy(); - } - [Fact] public void should_use_logger_factory() { @@ -140,15 +84,6 @@ namespace Ocelot.UnitTests.DependencyInjection .BDDfy(); } - [Fact] - public void should_set_up_tracing() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => WhenISetUpOpentracing()) - .When(x => WhenIAccessOcelotHttpTracingHandler()) - .BDDfy(); - } - [Fact] public void should_set_up_without_passing_in_config() { @@ -191,15 +126,6 @@ namespace Ocelot.UnitTests.DependencyInjection _ocelotBuilder.AddTransientDefinedAggregator(); } - private void ThenTheSpecificHandlersAreSingleton() - { - var handlers = _serviceProvider.GetServices().ToList(); - var first = handlers[0]; - handlers = _serviceProvider.GetServices().ToList(); - var second = handlers[0]; - first.ShouldBe(second); - } - private void ThenTheSpecificHandlersAreTransient() { var handlers = _serviceProvider.GetServices().ToList(); @@ -218,16 +144,6 @@ namespace Ocelot.UnitTests.DependencyInjection first.ShouldNotBe(second); } - private void WhenISetUpAdministration() - { - _ocelotBuilder.AddAdministration("/administration", "secret"); - } - - private void WhenISetUpAdministration(Action options) - { - _ocelotBuilder.AddAdministration("/administration", options); - } - private void AddTransientGlobalDelegatingHandler() where T : DelegatingHandler { @@ -240,13 +156,6 @@ namespace Ocelot.UnitTests.DependencyInjection _ocelotBuilder.AddDelegatingHandler(); } - private void ThenTheCorrectAdminPathIsRegitered() - { - _serviceProvider = _services.BuildServiceProvider(); - var path = _serviceProvider.GetService(); - path.Path.ShouldBe("/administration"); - } - private void ThenTheProviderIsRegisteredAndReturnsHandlers() { _serviceProvider = _services.BuildServiceProvider(); @@ -289,60 +198,6 @@ namespace Ocelot.UnitTests.DependencyInjection first.ShouldBe(second); } - private void OnlyOneVersionOfEachCacheIsRegistered() - { - var outputCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); - var outputCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); - var instance = (ICacheManager)outputCacheManager.ImplementationInstance; - var ocelotConfigCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); - var ocelotConfigCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); - var fileConfigCache = _services.Single(x => x.ServiceType == typeof(IOcelotCache)); - var fileConfigCacheManager = _services.Single(x => x.ServiceType == typeof(ICacheManager)); - - instance.Configuration.MaxRetries.ShouldBe(_maxRetries); - outputCache.ShouldNotBeNull(); - ocelotConfigCache.ShouldNotBeNull(); - ocelotConfigCacheManager.ShouldNotBeNull(); - fileConfigCache.ShouldNotBeNull(); - fileConfigCacheManager.ShouldNotBeNull(); - } - - private void WhenISetUpConsul() - { - try - { - _ocelotBuilder.AddStoreOcelotConfigurationInConsul(); - } - catch (Exception e) - { - _ex = e; - } - } - - private void WhenISetUpRafty() - { - try - { - _ocelotBuilder.AddAdministration("/administration", "secret").AddRafty(); - } - catch (Exception e) - { - _ex = e; - } - } - - private void AddGlobalDelegatingHandler() - where T : DelegatingHandler - { - _ocelotBuilder.AddDelegatingHandler(true); - } - - private void AddSpecificDelegatingHandler() - where T : DelegatingHandler - { - _ocelotBuilder.AddDelegatingHandler(); - } - private void ThenAnOcelotBuilderIsReturned() { _ocelotBuilder.ShouldBeOfType(); @@ -372,39 +227,6 @@ namespace Ocelot.UnitTests.DependencyInjection } } - private void WhenISetUpCacheManager() - { - try - { - _ocelotBuilder.AddCacheManager(x => { - x.WithMaxRetries(_maxRetries); - x.WithDictionaryHandle(); - }); - } - catch (Exception e) - { - _ex = e; - } - } - - private void WhenISetUpOpentracing() - { - try - { - _ocelotBuilder.AddOpenTracing( - option => - { - option.CollectorUrl = "http://localhost:9618"; - option.Service = "Ocelot.ManualTest"; - } - ); - } - catch (Exception e) - { - _ex = e; - } - } - private void WhenIAccessLoggerFactory() { try @@ -419,19 +241,6 @@ namespace Ocelot.UnitTests.DependencyInjection } } - private void WhenIAccessOcelotHttpTracingHandler() - { - try - { - var tracingHandler = _serviceProvider.GetService(); - tracingHandler.ShouldNotBeNull(); - } - catch (Exception e) - { - _ex = e; - } - } - private void WhenIValidateScopes() { try diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs index b29d2d29..a1c4b799 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteCreatorTests.cs @@ -1,9 +1,3 @@ -using Ocelot.DownstreamRouteFinder.Finder; -using Xunit; -using Shouldly; -using Ocelot.Configuration; -using System.Net.Http; - namespace Ocelot.UnitTests.DownstreamRouteFinder { using System; @@ -14,6 +8,12 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder using Ocelot.LoadBalancer.LoadBalancers; using Responses; using TestStack.BDDfy; + using Ocelot.DownstreamRouteFinder.Finder; + using Xunit; + using Shouldly; + using Ocelot.Configuration; + using System.Net.Http; + using System.Collections.Generic; public class DownstreamRouteCreatorTests { @@ -53,6 +53,34 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .BDDfy(); } + [Fact] + public void should_create_downstream_route_with_rate_limit_options() + { + var rateLimitOptions = new RateLimitOptionsBuilder() + .WithEnableRateLimiting(true) + .WithClientIdHeader("test") + .Build(); + + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithServiceName("auth") + .WithRateLimitOptions(rateLimitOptions) + .Build(); + + var reRoute = new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .Build(); + + var reRoutes = new List { reRoute }; + + var configuration = new InternalConfiguration(reRoutes, "doesnt matter", null, "doesnt matter", _loadBalancerOptions, "http", _qoSOptions, _handlerOptions); + + this.Given(_ => GivenTheConfiguration(configuration)) + .When(_ => WhenICreate()) + .Then(_ => ThenTheDownstreamRouteIsCreated()) + .And(_ => WithRateLimitOptions(rateLimitOptions)) + .BDDfy(); + } + [Fact] public void should_cache_downstream_route() { @@ -174,6 +202,13 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .Returns(options); } + private void WithRateLimitOptions(RateLimitOptions expected) + { + _result.Data.ReRoute.DownstreamReRoute[0].EnableEndpointEndpointRateLimiting.ShouldBeTrue(); + _result.Data.ReRoute.DownstreamReRoute[0].RateLimitOptions.EnableRateLimiting.ShouldBe(expected.EnableRateLimiting); + _result.Data.ReRoute.DownstreamReRoute[0].RateLimitOptions.ClientIdHeader.ShouldBe(expected.ClientIdHeader); + } + private void ThenTheDownstreamRouteIsCreated() { _result.Data.ReRoute.DownstreamReRoute[0].DownstreamPathTemplate.Value.ShouldBe("/test"); diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs index 2b595660..0380f2fc 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteProviderFactoryTests.cs @@ -53,6 +53,21 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .BDDfy(); } + [Fact] + public void should_return_downstream_route_finder_when_not_dynamic_re_route_and_service_discovery_on() + { + var spConfig = new ServiceProviderConfigurationBuilder().WithHost("test").WithPort(50).WithType("test").Build(); + var reRoutes = new List + { + new ReRouteBuilder().WithUpstreamPathTemplate("woot").Build() + }; + + this.Given(_ => GivenTheReRoutes(reRoutes, spConfig)) + .When(_ => WhenIGet()) + .Then(_ => ThenTheResultShouldBe()) + .BDDfy(); + } + [Fact] public void should_return_downstream_route_finder_as_no_service_discovery_given_no_host() { @@ -101,6 +116,21 @@ namespace Ocelot.UnitTests.DownstreamRouteFinder .BDDfy(); } + [Fact] + public void should_return_downstream_route_creator_with_dynamic_re_route() + { + var spConfig = new ServiceProviderConfigurationBuilder().WithHost("test").WithPort(50).WithType("test").Build(); + var reRoutes = new List + { + new ReRouteBuilder().Build() + }; + + this.Given(_ => GivenTheReRoutes(reRoutes, spConfig)) + .When(_ => WhenIGet()) + .Then(_ => ThenTheResultShouldBe()) + .BDDfy(); + } + private void ThenTheResultShouldBe() { _result.ShouldBeOfType(); diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index ac59b736..2da93538 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -289,6 +289,39 @@ .BDDfy(); } + [Fact] + public void issue_473_should_not_remove_additional_query_string() + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("/Authorized/{action}?server={server}") + .WithUpstreamHttpMethod(new List { "Post", "Get" }) + .WithDownstreamScheme("http") + .WithUpstreamPathTemplate("/uc/Authorized/{server}/{action}") + .Build(); + + var config = new ServiceProviderConfigurationBuilder() + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs( + new DownstreamRoute( + new List + { + new PlaceholderNameAndValue("{action}", "1"), + new PlaceholderNameAndValue("{server}", "2") + }, + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .WithUpstreamHttpMethod(new List { "Post", "Get" }) + .Build()))) + .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:5000/uc/Authorized/2/1/refresh?refreshToken=2288356cfb1338fdc5ff4ca558ec785118dfe1ff2864340937da8226863ff66d")) + .And(x => GivenTheServiceProviderConfigIs(config)) + .And(x => x.GivenTheUrlReplacerWillReturn("/Authorized/1?server=2")) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamRequestUriIs("http://localhost:5000/Authorized/1?refreshToken=2288356cfb1338fdc5ff4ca558ec785118dfe1ff2864340937da8226863ff66d&server=2")) + .And(x => ThenTheQueryStringIs("?refreshToken=2288356cfb1338fdc5ff4ca558ec785118dfe1ff2864340937da8226863ff66d&server=2")) + .BDDfy(); + } + private void GivenTheServiceProviderConfigIs(ServiceProviderConfiguration config) { var configuration = new InternalConfiguration(null, null, config, null, null, null, null, null); diff --git a/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs b/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs index ce88b468..0ca0b659 100644 --- a/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs @@ -1,197 +1,197 @@ -namespace Ocelot.UnitTests.Errors -{ - using System; - using System.Net; - using System.Threading.Tasks; - using Ocelot.Errors.Middleware; - using Ocelot.Logging; - using Shouldly; - using TestStack.BDDfy; - using Xunit; - using Microsoft.AspNetCore.Http; - using Moq; - using Ocelot.Configuration; - using Ocelot.Errors; - using Ocelot.Infrastructure.RequestData; - using Ocelot.Middleware; - using Ocelot.Configuration.Repository; - - public class ExceptionHandlerMiddlewareTests - { - bool _shouldThrowAnException; - private readonly Mock _configRepo; - private readonly Mock _repo; - private Mock _loggerFactory; - private Mock _logger; - private readonly ExceptionHandlerMiddleware _middleware; - private readonly DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; - - public ExceptionHandlerMiddlewareTests() - { - _configRepo = new Mock(); - _repo = new Mock(); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - _loggerFactory = new Mock(); - _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = async context => { - await Task.CompletedTask; - - if (_shouldThrowAnException) - { - throw new Exception("BOOM"); - } - - context.HttpContext.Response.StatusCode = (int)HttpStatusCode.OK; - }; - _middleware = new ExceptionHandlerMiddleware(_next, _loggerFactory.Object, _configRepo.Object, _repo.Object); - } - - [Fact] - public void NoDownstreamException() - { - var config = new InternalConfiguration(null, null, null, null, null, null, null, null); - - this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) - .And(_ => GivenTheConfigurationIs(config)) - .When(_ => WhenICallTheMiddleware()) - .Then(_ => ThenTheResponseIsOk()) - .And(_ => TheAspDotnetRequestIdIsSet()) - .BDDfy(); - } - - [Fact] - public void DownstreamException() - { - var config = new InternalConfiguration(null, null, null, null, null, null, null, null); - - this.Given(_ => GivenAnExceptionWillBeThrownDownstream()) - .And(_ => GivenTheConfigurationIs(config)) - .When(_ => WhenICallTheMiddleware()) - .Then(_ => ThenTheResponseIsError()) - .BDDfy(); - } - - [Fact] - public void ShouldSetRequestId() - { - var config = new InternalConfiguration(null, null, null, "requestidkey", null, null, null, null); - - this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) - .And(_ => GivenTheConfigurationIs(config)) - .When(_ => WhenICallTheMiddlewareWithTheRequestIdKey("requestidkey", "1234")) - .Then(_ => ThenTheResponseIsOk()) - .And(_ => TheRequestIdIsSet("RequestId", "1234")) - .BDDfy(); - } - - [Fact] - public void ShouldSetAspDotNetRequestId() - { - var config = new InternalConfiguration(null, null, null, null, null, null, null, null); - - this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) - .And(_ => GivenTheConfigurationIs(config)) - .When(_ => WhenICallTheMiddlewareWithTheRequestIdKey("requestidkey", "1234")) - .Then(_ => ThenTheResponseIsOk()) - .And(_ => TheAspDotnetRequestIdIsSet()) - .BDDfy(); - } - - [Fact] - public void should_throw_exception_if_config_provider_returns_error() - { - this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) - .And(_ => GivenTheConfigReturnsError()) - .When(_ => WhenICallTheMiddlewareWithTheRequestIdKey("requestidkey", "1234")) - .Then(_ => ThenAnExceptionIsThrown()) - .BDDfy(); - } - - [Fact] - public void should_throw_exception_if_config_provider_throws() - { - this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) - .And(_ => GivenTheConfigThrows()) - .When(_ => WhenICallTheMiddlewareWithTheRequestIdKey("requestidkey", "1234")) - .Then(_ => ThenAnExceptionIsThrown()) - .BDDfy(); - } - - private void WhenICallTheMiddlewareWithTheRequestIdKey(string key, string value) - { - _downstreamContext.HttpContext.Request.Headers.Add(key, value); - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - } - - private void WhenICallTheMiddleware() - { - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - } - - private void GivenTheConfigThrows() - { - var ex = new Exception("outer", new Exception("inner")); - _configRepo - .Setup(x => x.Get()).Throws(ex); - } - - private void ThenAnExceptionIsThrown() - { - _downstreamContext.HttpContext.Response.StatusCode.ShouldBe(500); - } - - private void GivenTheConfigReturnsError() - { - var response = new Responses.ErrorResponse(new FakeError()); - _configRepo - .Setup(x => x.Get()).Returns(response); - } - - private void TheRequestIdIsSet(string key, string value) - { - _repo.Verify(x => x.Add(key, value), Times.Once); - } - - private void GivenTheConfigurationIs(IInternalConfiguration config) - { - var response = new Responses.OkResponse(config); - _configRepo - .Setup(x => x.Get()).Returns(response); - } - - private void GivenAnExceptionWillNotBeThrownDownstream() - { - _shouldThrowAnException = false; - } - - private void GivenAnExceptionWillBeThrownDownstream() - { - _shouldThrowAnException = true; - } - - private void ThenTheResponseIsOk() - { - _downstreamContext.HttpContext.Response.StatusCode.ShouldBe(200); - } - - private void ThenTheResponseIsError() - { - _downstreamContext.HttpContext.Response.StatusCode.ShouldBe(500); - } - - private void TheAspDotnetRequestIdIsSet() - { - _repo.Verify(x => x.Add(It.IsAny(), It.IsAny()), Times.Once); - } - - class FakeError : Error - { - internal FakeError() - : base("meh", OcelotErrorCode.CannotAddDataError) - { - } - } - } -} +namespace Ocelot.UnitTests.Errors +{ + using System; + using System.Net; + using System.Threading.Tasks; + using Ocelot.Errors.Middleware; + using Ocelot.Logging; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + using Microsoft.AspNetCore.Http; + using Moq; + using Ocelot.Configuration; + using Ocelot.Errors; + using Ocelot.Infrastructure.RequestData; + using Ocelot.Middleware; + using Ocelot.Configuration.Repository; + + public class ExceptionHandlerMiddlewareTests + { + bool _shouldThrowAnException; + private readonly Mock _configRepo; + private readonly Mock _repo; + private Mock _loggerFactory; + private Mock _logger; + private readonly ExceptionHandlerMiddleware _middleware; + private readonly DownstreamContext _downstreamContext; + private OcelotRequestDelegate _next; + + public ExceptionHandlerMiddlewareTests() + { + _configRepo = new Mock(); + _repo = new Mock(); + _downstreamContext = new DownstreamContext(new DefaultHttpContext()); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = async context => { + await Task.CompletedTask; + + if (_shouldThrowAnException) + { + throw new Exception("BOOM"); + } + + context.HttpContext.Response.StatusCode = (int)HttpStatusCode.OK; + }; + _middleware = new ExceptionHandlerMiddleware(_next, _loggerFactory.Object, _configRepo.Object, _repo.Object); + } + + [Fact] + public void NoDownstreamException() + { + var config = new InternalConfiguration(null, null, null, null, null, null, null, null); + + this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) + .And(_ => GivenTheConfigurationIs(config)) + .When(_ => WhenICallTheMiddleware()) + .Then(_ => ThenTheResponseIsOk()) + .And(_ => TheAspDotnetRequestIdIsSet()) + .BDDfy(); + } + + [Fact] + public void DownstreamException() + { + var config = new InternalConfiguration(null, null, null, null, null, null, null, null); + + this.Given(_ => GivenAnExceptionWillBeThrownDownstream()) + .And(_ => GivenTheConfigurationIs(config)) + .When(_ => WhenICallTheMiddleware()) + .Then(_ => ThenTheResponseIsError()) + .BDDfy(); + } + + [Fact] + public void ShouldSetRequestId() + { + var config = new InternalConfiguration(null, null, null, "requestidkey", null, null, null, null); + + this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) + .And(_ => GivenTheConfigurationIs(config)) + .When(_ => WhenICallTheMiddlewareWithTheRequestIdKey("requestidkey", "1234")) + .Then(_ => ThenTheResponseIsOk()) + .And(_ => TheRequestIdIsSet("RequestId", "1234")) + .BDDfy(); + } + + [Fact] + public void ShouldSetAspDotNetRequestId() + { + var config = new InternalConfiguration(null, null, null, null, null, null, null, null); + + this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) + .And(_ => GivenTheConfigurationIs(config)) + .When(_ => WhenICallTheMiddlewareWithTheRequestIdKey("requestidkey", "1234")) + .Then(_ => ThenTheResponseIsOk()) + .And(_ => TheAspDotnetRequestIdIsSet()) + .BDDfy(); + } + + [Fact] + public void should_throw_exception_if_config_provider_returns_error() + { + this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) + .And(_ => GivenTheConfigReturnsError()) + .When(_ => WhenICallTheMiddlewareWithTheRequestIdKey("requestidkey", "1234")) + .Then(_ => ThenAnExceptionIsThrown()) + .BDDfy(); + } + + [Fact] + public void should_throw_exception_if_config_provider_throws() + { + this.Given(_ => GivenAnExceptionWillNotBeThrownDownstream()) + .And(_ => GivenTheConfigThrows()) + .When(_ => WhenICallTheMiddlewareWithTheRequestIdKey("requestidkey", "1234")) + .Then(_ => ThenAnExceptionIsThrown()) + .BDDfy(); + } + + private void WhenICallTheMiddlewareWithTheRequestIdKey(string key, string value) + { + _downstreamContext.HttpContext.Request.Headers.Add(key, value); + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + } + + private void WhenICallTheMiddleware() + { + _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + } + + private void GivenTheConfigThrows() + { + var ex = new Exception("outer", new Exception("inner")); + _configRepo + .Setup(x => x.Get()).Throws(ex); + } + + private void ThenAnExceptionIsThrown() + { + _downstreamContext.HttpContext.Response.StatusCode.ShouldBe(500); + } + + private void GivenTheConfigReturnsError() + { + var response = new Responses.ErrorResponse(new FakeError()); + _configRepo + .Setup(x => x.Get()).Returns(response); + } + + private void TheRequestIdIsSet(string key, string value) + { + _repo.Verify(x => x.Add(key, value), Times.Once); + } + + private void GivenTheConfigurationIs(IInternalConfiguration config) + { + var response = new Responses.OkResponse(config); + _configRepo + .Setup(x => x.Get()).Returns(response); + } + + private void GivenAnExceptionWillNotBeThrownDownstream() + { + _shouldThrowAnException = false; + } + + private void GivenAnExceptionWillBeThrownDownstream() + { + _shouldThrowAnException = true; + } + + private void ThenTheResponseIsOk() + { + _downstreamContext.HttpContext.Response.StatusCode.ShouldBe(200); + } + + private void ThenTheResponseIsError() + { + _downstreamContext.HttpContext.Response.StatusCode.ShouldBe(500); + } + + private void TheAspDotnetRequestIdIsSet() + { + _repo.Verify(x => x.Add(It.IsAny(), It.IsAny()), Times.Once); + } + + class FakeError : Error + { + internal FakeError() + : base("meh", OcelotErrorCode.CannotAddDataError) + { + } + } + } +} diff --git a/test/Ocelot.UnitTests/Infrastructure/InMemoryBusTests.cs b/test/Ocelot.UnitTests/Infrastructure/InMemoryBusTests.cs index 35849dbf..ed8f3e00 100644 --- a/test/Ocelot.UnitTests/Infrastructure/InMemoryBusTests.cs +++ b/test/Ocelot.UnitTests/Infrastructure/InMemoryBusTests.cs @@ -22,7 +22,7 @@ namespace Ocelot.UnitTests.Infrastructure called = true; }); _bus.Publish(new object(), 1); - await Task.Delay(10); + await Task.Delay(100); called.ShouldBeTrue(); } diff --git a/test/Ocelot.UnitTests/Logging/OcelotDiagnosticListenerTests.cs b/test/Ocelot.UnitTests/Logging/OcelotDiagnosticListenerTests.cs index 3703ddb0..1a463cfa 100644 --- a/test/Ocelot.UnitTests/Logging/OcelotDiagnosticListenerTests.cs +++ b/test/Ocelot.UnitTests/Logging/OcelotDiagnosticListenerTests.cs @@ -7,6 +7,7 @@ using Xunit; using Ocelot.Middleware; using Microsoft.AspNetCore.Http; using System; +using Microsoft.Extensions.DependencyInjection; namespace Ocelot.UnitTests.Logging { @@ -15,7 +16,8 @@ namespace Ocelot.UnitTests.Logging private readonly OcelotDiagnosticListener _listener; private Mock _factory; private readonly Mock _logger; - private IServiceTracer _tracer; + private IServiceCollection _serviceCollection; + private IServiceProvider _serviceProvider; private DownstreamContext _downstreamContext; private string _name; private Exception _exception; @@ -24,9 +26,10 @@ namespace Ocelot.UnitTests.Logging { _factory = new Mock(); _logger = new Mock(); - _tracer = new FakeServiceTracer(); + _serviceCollection = new ServiceCollection(); + _serviceProvider = _serviceCollection.BuildServiceProvider(); _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _listener = new OcelotDiagnosticListener(_factory.Object, _tracer); + _listener = new OcelotDiagnosticListener(_factory.Object, _serviceProvider); } [Fact] diff --git a/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs b/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs index 74f6c787..e14dddb5 100644 --- a/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs +++ b/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs @@ -3,12 +3,14 @@ namespace Ocelot.UnitTests.Middleware using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Ocelot.DependencyInjection; + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.DownstreamUrlCreator.Middleware; + using Ocelot.LoadBalancer.Middleware; using Ocelot.Middleware; using Ocelot.Middleware.Pipeline; - using Pivotal.Discovery.Client; + using Ocelot.Request.Middleware; + using Ocelot.WebSockets.Middleware; using Shouldly; - using Steeltoe.Common.Discovery; - using Steeltoe.Discovery.Eureka; using TestStack.BDDfy; using Xunit; @@ -26,6 +28,15 @@ namespace Ocelot.UnitTests.Middleware .BDDfy(); } + [Fact] + public void should_expand_pipeline() + { + this.Given(_ => GivenTheDepedenciesAreSetUp()) + .When(_ => WhenIExpandBuild()) + .Then(_ => ThenThePipelineIsBuilt()) + .BDDfy(); + } + private void ThenThePipelineIsBuilt() { _handlers.ShouldNotBeNull(); @@ -36,21 +47,28 @@ namespace Ocelot.UnitTests.Middleware _handlers = _builder.BuildOcelotPipeline(new OcelotPipelineConfiguration()); } + private void WhenIExpandBuild() + { + OcelotPipelineConfiguration configuration = new OcelotPipelineConfiguration(); + configuration.MapWhenOcelotPipeline.Add((app) => + { + app.UseDownstreamRouteFinderMiddleware(); + app.UseDownstreamRequestInitialiser(); + app.UseLoadBalancingMiddleware(); + app.UseDownstreamUrlCreatorMiddleware(); + app.UseWebSocketsProxyMiddleware(); + + return context => context.HttpContext.WebSockets.IsWebSocketRequest; + }); + _handlers = _builder.BuildOcelotPipeline(new OcelotPipelineConfiguration()); + } + private void GivenTheDepedenciesAreSetUp() { IConfigurationBuilder test = new ConfigurationBuilder(); var root = test.Build(); var services = new ServiceCollection(); services.AddSingleton(root); - services.AddDiscoveryClient(new DiscoveryOptions - { - ClientType = DiscoveryClientType.EUREKA, - ClientOptions = new EurekaClientOptions() - { - ShouldFetchRegistry = false, - ShouldRegisterWithEureka = false - } - }); services.AddOcelot(); var provider = services.BuildServiceProvider(); _builder = new OcelotPipelineBuilder(provider); diff --git a/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs b/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs index 21649a80..a798ae44 100644 --- a/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs +++ b/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs @@ -79,6 +79,8 @@ namespace Ocelot.UnitTests.Middleware del.Invoke(_downstreamContext); } + + private void ThenTheFuncIsInThePipeline() { _counter.ShouldBe(1); diff --git a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj index cb7110b4..7a08589a 100644 --- a/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj +++ b/test/Ocelot.UnitTests/Ocelot.UnitTests.csproj @@ -57,7 +57,6 @@ - diff --git a/test/Ocelot.UnitTests/Raft/OcelotFiniteStateMachineTests.cs b/test/Ocelot.UnitTests/Raft/OcelotFiniteStateMachineTests.cs deleted file mode 100644 index 669b6bf0..00000000 --- a/test/Ocelot.UnitTests/Raft/OcelotFiniteStateMachineTests.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Moq; -using Ocelot.Configuration.Setter; -using Ocelot.Raft; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Raft -{ - public class OcelotFiniteStateMachineTests - { - private UpdateFileConfiguration _command; - private OcelotFiniteStateMachine _fsm; - private Mock _setter; - - public OcelotFiniteStateMachineTests() - { - _setter = new Mock(); - _fsm = new OcelotFiniteStateMachine(_setter.Object); - } - - [Fact] - public void should_handle_update_file_configuration_command() - { - this.Given(x => GivenACommand(new UpdateFileConfiguration(new Ocelot.Configuration.File.FileConfiguration()))) - .When(x => WhenTheCommandIsHandled()) - .Then(x => ThenTheStateIsUpdated()) - .BDDfy(); - } - - private void GivenACommand(UpdateFileConfiguration command) - { - _command = command; - } - - private void WhenTheCommandIsHandled() - { - _fsm.Handle(new Rafty.Log.LogEntry(_command, _command.GetType(), 0)); - } - - private void ThenTheStateIsUpdated() - { - _setter.Verify(x => x.Set(_command.Configuration), Times.Once); - } - } -} \ No newline at end of file diff --git a/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs b/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs index 22ff9276..9be76e3f 100644 --- a/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs +++ b/test/Ocelot.UnitTests/Request/Mapper/RequestMapperTests.cs @@ -1,407 +1,442 @@ -namespace Ocelot.UnitTests.Request.Mapper -{ - using System.Collections.Generic; - using System.Linq; - using System.Net.Http; - - using Microsoft.AspNetCore.Http; - using Microsoft.AspNetCore.Http.Internal; - using Microsoft.Extensions.Primitives; - using Ocelot.Request.Mapper; - using Ocelot.Responses; - using TestStack.BDDfy; - using Xunit; - using Shouldly; - using System; - using System.IO; - using System.Text; - using System.Security.Cryptography; - - public class RequestMapperTests - { - readonly HttpRequest _inputRequest; - - readonly RequestMapper _requestMapper; - - Response _mappedRequest; - - List> _inputHeaders = null; - - public RequestMapperTests() - { - _inputRequest = new DefaultHttpRequest(new DefaultHttpContext()); - - _requestMapper = new RequestMapper(); - } - - [Theory] - [InlineData("https", "my.url:123", "/abc/DEF", "?a=1&b=2", "https://my.url:123/abc/DEF?a=1&b=2")] - [InlineData("http", "blah.com", "/d ef", "?abc=123", "http://blah.com/d%20ef?abc=123")] // note! the input is encoded when building the input request - [InlineData("http", "myusername:mypassword@abc.co.uk", null, null, "http://myusername:mypassword@abc.co.uk/")] - [InlineData("http", "點看.com", null, null, "http://xn--c1yn36f.com/")] - [InlineData("http", "xn--c1yn36f.com", null, null, "http://xn--c1yn36f.com/")] - public void Should_map_valid_request_uri(string scheme, string host, string path, string queryString, string expectedUri) - { - this.Given(_ => GivenTheInputRequestHasMethod("GET")) - .And(_ => GivenTheInputRequestHasScheme(scheme)) - .And(_ => GivenTheInputRequestHasHost(host)) - .And(_ => GivenTheInputRequestHasPath(path)) - .And(_ => GivenTheInputRequestHasQueryString(queryString)) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasUri(expectedUri)) - .BDDfy(); - } - - [Theory] - [InlineData("ftp", "google.com", "/abc/DEF", "?a=1&b=2")] - public void Should_error_on_unsupported_request_uri(string scheme, string host, string path, string queryString) - { - this.Given(_ => GivenTheInputRequestHasMethod("GET")) - .And(_ => GivenTheInputRequestHasScheme(scheme)) - .And(_ => GivenTheInputRequestHasHost(host)) - .And(_ => GivenTheInputRequestHasPath(path)) - .And(_ => GivenTheInputRequestHasQueryString(queryString)) - .When(_ => WhenMapped()) - .Then(_ => ThenAnErrorIsReturned()) - .And(_ => ThenTheMappedRequestIsNull()) - .BDDfy(); - } - - [Theory] - [InlineData("GET")] - [InlineData("POST")] - [InlineData("WHATEVER")] - public void Should_map_method(string method) - { - this.Given(_ => GivenTheInputRequestHasMethod(method)) - .And(_ => GivenTheInputRequestHasAValidUri()) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasMethod(method)) - .BDDfy(); - } - - [Fact] - public void Should_map_all_headers() - { - this.Given(_ => GivenTheInputRequestHasHeaders()) - .And(_ => GivenTheInputRequestHasMethod("GET")) - .And(_ => GivenTheInputRequestHasAValidUri()) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasEachHeader()) - .BDDfy(); - } - - [Fact] - public void Should_handle_no_headers() - { - this.Given(_ => GivenTheInputRequestHasNoHeaders()) - .And(_ => GivenTheInputRequestHasMethod("GET")) - .And(_ => GivenTheInputRequestHasAValidUri()) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasNoHeaders()) - .BDDfy(); - } - - [Fact] - public void Should_map_content() - { - this.Given(_ => GivenTheInputRequestHasContent("This is my content")) - .And(_ => GivenTheInputRequestHasMethod("GET")) - .And(_ => GivenTheInputRequestHasAValidUri()) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasContent("This is my content")) - .BDDfy(); - } - - [Fact] - public void Should_handle_no_content() - { - this.Given(_ => GivenTheInputRequestHasNoContent()) - .And(_ => GivenTheInputRequestHasMethod("GET")) - .And(_ => GivenTheInputRequestHasAValidUri()) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasNoContent()) - .BDDfy(); - } - - [Fact] - public void Should_map_content_headers() - { - byte[] md5bytes = new byte[0]; - using (var md5 = MD5.Create()) - { - md5bytes = md5.ComputeHash(Encoding.UTF8.GetBytes("some md5")); - } - - this.Given(_ => GivenTheInputRequestHasContent("This is my content")) - .And(_ => GivenTheContentTypeIs("application/json")) - .And(_ => GivenTheContentEncodingIs("gzip, compress")) - .And(_ => GivenTheContentLanguageIs("english")) - .And(_ => GivenTheContentLocationIs("/my-receipts/38")) - .And(_ => GivenTheContentRangeIs("bytes 1-2/*")) - .And(_ => GivenTheContentDispositionIs("inline")) - .And(_ => GivenTheContentMD5Is(md5bytes)) - .And(_ => GivenTheInputRequestHasMethod("GET")) - .And(_ => GivenTheInputRequestHasAValidUri()) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasContentTypeHeader("application/json")) - .And(_ => ThenTheMappedRequestHasContentEncodingHeader("gzip", "compress")) - .And(_ => ThenTheMappedRequestHasContentLanguageHeader("english")) - .And(_ => ThenTheMappedRequestHasContentLocationHeader("/my-receipts/38")) - .And(_ => ThenTheMappedRequestHasContentMD5Header(md5bytes)) - .And(_ => ThenTheMappedRequestHasContentRangeHeader()) - .And(_ => ThenTheMappedRequestHasContentDispositionHeader("inline")) - .And(_ => ThenTheMappedRequestHasContentSize("This is my content".Length)) - .And(_ => ThenTheContentHeadersAreNotAddedToNonContentHeaders()) - .BDDfy(); - } - - [Fact] - public void should_not_add_content_headers() - { - this.Given(_ => GivenTheInputRequestHasContent("This is my content")) - .And(_ => GivenTheContentTypeIs("application/json")) - .And(_ => GivenTheInputRequestHasMethod("POST")) - .And(_ => GivenTheInputRequestHasAValidUri()) - .When(_ => WhenMapped()) - .Then(_ => ThenNoErrorIsReturned()) - .And(_ => ThenTheMappedRequestHasContentTypeHeader("application/json")) - .And(_ => ThenTheMappedRequestHasContentSize("This is my content".Length)) - .And(_ => ThenTheOtherContentTypeHeadersAreNotMapped()) - .BDDfy(); - } - - private void ThenTheContentHeadersAreNotAddedToNonContentHeaders() - { - _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-Disposition"); - _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentMD5"); - _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentRange"); - _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentLanguage"); - _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentEncoding"); - _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentLocation"); - _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-Length"); - _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-Type"); - } - - private void ThenTheOtherContentTypeHeadersAreNotMapped() - { - _mappedRequest.Data.Content.Headers.ContentDisposition.ShouldBeNull(); - _mappedRequest.Data.Content.Headers.ContentMD5.ShouldBeNull(); - _mappedRequest.Data.Content.Headers.ContentRange.ShouldBeNull(); - _mappedRequest.Data.Content.Headers.ContentLanguage.ShouldBeEmpty(); - _mappedRequest.Data.Content.Headers.ContentEncoding.ShouldBeEmpty(); - _mappedRequest.Data.Content.Headers.ContentLocation.ShouldBeNull(); - } - - private void ThenTheMappedRequestHasContentDispositionHeader(string expected) - { - _mappedRequest.Data.Content.Headers.ContentDisposition.DispositionType.ShouldBe(expected); - } - - private void GivenTheContentDispositionIs(string input) - { - _inputRequest.Headers.Add("Content-Disposition", input); - } - - private void ThenTheMappedRequestHasContentMD5Header(byte[] expected) - { - _mappedRequest.Data.Content.Headers.ContentMD5.ShouldBe(expected); - } - - private void GivenTheContentMD5Is(byte[] input) - { - var base64 = Convert.ToBase64String(input); - _inputRequest.Headers.Add("Content-MD5", base64); - } - - private void ThenTheMappedRequestHasContentRangeHeader() - { - _mappedRequest.Data.Content.Headers.ContentRange.From.ShouldBe(1); - _mappedRequest.Data.Content.Headers.ContentRange.To.ShouldBe(2); - } - - private void GivenTheContentRangeIs(string input) - { - _inputRequest.Headers.Add("Content-Range", input); - } - - private void ThenTheMappedRequestHasContentLocationHeader(string expected) - { - _mappedRequest.Data.Content.Headers.ContentLocation.OriginalString.ShouldBe(expected); - } - - private void GivenTheContentLocationIs(string input) - { - _inputRequest.Headers.Add("Content-Location", input); - } - - private void ThenTheMappedRequestHasContentLanguageHeader(string expected) - { - _mappedRequest.Data.Content.Headers.ContentLanguage.First().ShouldBe(expected); - } - - private void GivenTheContentLanguageIs(string input) - { - _inputRequest.Headers.Add("Content-Language", input); - } - - private void ThenTheMappedRequestHasContentEncodingHeader(string expected, string expectedTwo) - { - _mappedRequest.Data.Content.Headers.ContentEncoding.ToArray()[0].ShouldBe(expected); - _mappedRequest.Data.Content.Headers.ContentEncoding.ToArray()[1].ShouldBe(expectedTwo); - } - - private void GivenTheContentEncodingIs(string input) - { - _inputRequest.Headers.Add("Content-Encoding", input); - } - - private void GivenTheContentTypeIs(string contentType) - { - _inputRequest.ContentType = contentType; - } - - private void ThenTheMappedRequestHasContentTypeHeader(string expected) - { - _mappedRequest.Data.Content.Headers.ContentType.MediaType.ShouldBe(expected); - } - - private void ThenTheMappedRequestHasContentSize(long expected) - { - _mappedRequest.Data.Content.Headers.ContentLength.ShouldBe(expected); - } - - private void GivenTheInputRequestHasMethod(string method) - { - _inputRequest.Method = method; - } - - private void GivenTheInputRequestHasScheme(string scheme) - { - _inputRequest.Scheme = scheme; - } - - private void GivenTheInputRequestHasHost(string host) - { - _inputRequest.Host = new HostString(host); - } - - private void GivenTheInputRequestHasPath(string path) - { - if (path != null) - { - _inputRequest.Path = path; - } - } - - private void GivenTheInputRequestHasQueryString(string querystring) - { - if (querystring != null) - { - _inputRequest.QueryString = new QueryString(querystring); - } - } - - private void GivenTheInputRequestHasAValidUri() - { - GivenTheInputRequestHasScheme("http"); - GivenTheInputRequestHasHost("www.google.com"); - } - - private void GivenTheInputRequestHasHeaders() - { - _inputHeaders = new List>() - { - new KeyValuePair("abc", new StringValues(new string[]{"123","456" })), - new KeyValuePair("def", new StringValues(new string[]{"789","012" })), - }; - - foreach (var inputHeader in _inputHeaders) - { - _inputRequest.Headers.Add(inputHeader); - } - } - - private void GivenTheInputRequestHasNoHeaders() - { - _inputRequest.Headers.Clear(); - } - - private void GivenTheInputRequestHasContent(string content) - { - _inputRequest.Body = new MemoryStream(Encoding.UTF8.GetBytes(content)); - } - - private void GivenTheInputRequestHasNoContent() - { - _inputRequest.Body = null; - } - - private void WhenMapped() - { - _mappedRequest = _requestMapper.Map(_inputRequest).GetAwaiter().GetResult(); - } - - private void ThenNoErrorIsReturned() - { - _mappedRequest.IsError.ShouldBeFalse(); - } - - private void ThenAnErrorIsReturned() - { - _mappedRequest.IsError.ShouldBeTrue(); - } - - private void ThenTheMappedRequestHasUri(string expectedUri) - { - _mappedRequest.Data.RequestUri.OriginalString.ShouldBe(expectedUri); - } - - private void ThenTheMappedRequestHasMethod(string expectedMethod) - { - _mappedRequest.Data.Method.ToString().ShouldBe(expectedMethod); - } - - private void ThenTheMappedRequestHasEachHeader() - { - _mappedRequest.Data.Headers.Count().ShouldBe(_inputHeaders.Count); - foreach(var header in _mappedRequest.Data.Headers) - { - var inputHeader = _inputHeaders.First(h => h.Key == header.Key); - inputHeader.ShouldNotBeNull(); - inputHeader.Value.Count().ShouldBe(header.Value.Count()); - foreach(var inputHeaderValue in inputHeader.Value) - { - header.Value.Any(v => v == inputHeaderValue); - } - } - } - - private void ThenTheMappedRequestHasNoHeaders() - { - _mappedRequest.Data.Headers.Count().ShouldBe(0); - } - - private void ThenTheMappedRequestHasContent(string expectedContent) - { - _mappedRequest.Data.Content.ReadAsStringAsync().GetAwaiter().GetResult().ShouldBe(expectedContent); - } - - private void ThenTheMappedRequestHasNoContent() - { - _mappedRequest.Data.Content.ShouldBeNull(); - } - - private void ThenTheMappedRequestIsNull() - { - _mappedRequest.Data.ShouldBeNull(); - } - } -} +namespace Ocelot.UnitTests.Request.Mapper +{ + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Http.Internal; + using Microsoft.Extensions.Primitives; + using Ocelot.Request.Mapper; + using Ocelot.Responses; + using TestStack.BDDfy; + using Xunit; + using Shouldly; + using System; + using System.IO; + using System.Text; + using System.Security.Cryptography; + using System.Threading.Tasks; + + public class RequestMapperTests + { + readonly HttpRequest _inputRequest; + + readonly RequestMapper _requestMapper; + + Response _mappedRequest; + + List> _inputHeaders = null; + + public RequestMapperTests() + { + _inputRequest = new DefaultHttpRequest(new DefaultHttpContext()); + + _requestMapper = new RequestMapper(); + } + + [Theory] + [InlineData("https", "my.url:123", "/abc/DEF", "?a=1&b=2", "https://my.url:123/abc/DEF?a=1&b=2")] + [InlineData("http", "blah.com", "/d ef", "?abc=123", "http://blah.com/d%20ef?abc=123")] // note! the input is encoded when building the input request + [InlineData("http", "myusername:mypassword@abc.co.uk", null, null, "http://myusername:mypassword@abc.co.uk/")] + [InlineData("http", "點看.com", null, null, "http://xn--c1yn36f.com/")] + [InlineData("http", "xn--c1yn36f.com", null, null, "http://xn--c1yn36f.com/")] + public void Should_map_valid_request_uri(string scheme, string host, string path, string queryString, string expectedUri) + { + this.Given(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasScheme(scheme)) + .And(_ => GivenTheInputRequestHasHost(host)) + .And(_ => GivenTheInputRequestHasPath(path)) + .And(_ => GivenTheInputRequestHasQueryString(queryString)) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasUri(expectedUri)) + .BDDfy(); + } + + [Theory] + [InlineData("ftp", "google.com", "/abc/DEF", "?a=1&b=2")] + public void Should_error_on_unsupported_request_uri(string scheme, string host, string path, string queryString) + { + this.Given(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasScheme(scheme)) + .And(_ => GivenTheInputRequestHasHost(host)) + .And(_ => GivenTheInputRequestHasPath(path)) + .And(_ => GivenTheInputRequestHasQueryString(queryString)) + .When(_ => WhenMapped()) + .Then(_ => ThenAnErrorIsReturned()) + .And(_ => ThenTheMappedRequestIsNull()) + .BDDfy(); + } + + [Theory] + [InlineData("GET")] + [InlineData("POST")] + [InlineData("WHATEVER")] + public void Should_map_method(string method) + { + this.Given(_ => GivenTheInputRequestHasMethod(method)) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasMethod(method)) + .BDDfy(); + } + + [Fact] + public void Should_map_all_headers() + { + this.Given(_ => GivenTheInputRequestHasHeaders()) + .And(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasEachHeader()) + .BDDfy(); + } + + [Fact] + public void Should_handle_no_headers() + { + this.Given(_ => GivenTheInputRequestHasNoHeaders()) + .And(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasNoHeaders()) + .BDDfy(); + } + + [Fact] + public void Should_map_content() + { + this.Given(_ => GivenTheInputRequestHasContent("This is my content")) + .And(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasContent("This is my content")) + .BDDfy(); + } + + [Fact] + public void Should_handle_no_content() + { + this.Given(_ => GivenTheInputRequestHasNullContent()) + .And(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasNoContent()) + .BDDfy(); + } + + [Fact] + public void Should_handle_no_content_type() + { + this.Given(_ => GivenTheInputRequestHasNoContentType()) + .And(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasNoContent()) + .BDDfy(); + } + + [Fact] + public void Should_handle_no_content_length() + { + this.Given(_ => GivenTheInputRequestHasNoContentLength()) + .And(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasNoContent()) + .BDDfy(); + } + + private void GivenTheInputRequestHasNoContentLength() + { + _inputRequest.ContentLength = null; + } + + private void GivenTheInputRequestHasNoContentType() + { + _inputRequest.ContentType = null; + } + + [Fact] + public void Should_map_content_headers() + { + byte[] md5bytes = new byte[0]; + using (var md5 = MD5.Create()) + { + md5bytes = md5.ComputeHash(Encoding.UTF8.GetBytes("some md5")); + } + + this.Given(_ => GivenTheInputRequestHasContent("This is my content")) + .And(_ => GivenTheContentTypeIs("application/json")) + .And(_ => GivenTheContentEncodingIs("gzip, compress")) + .And(_ => GivenTheContentLanguageIs("english")) + .And(_ => GivenTheContentLocationIs("/my-receipts/38")) + .And(_ => GivenTheContentRangeIs("bytes 1-2/*")) + .And(_ => GivenTheContentDispositionIs("inline")) + .And(_ => GivenTheContentMD5Is(md5bytes)) + .And(_ => GivenTheInputRequestHasMethod("GET")) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasContentTypeHeader("application/json")) + .And(_ => ThenTheMappedRequestHasContentEncodingHeader("gzip", "compress")) + .And(_ => ThenTheMappedRequestHasContentLanguageHeader("english")) + .And(_ => ThenTheMappedRequestHasContentLocationHeader("/my-receipts/38")) + .And(_ => ThenTheMappedRequestHasContentMD5Header(md5bytes)) + .And(_ => ThenTheMappedRequestHasContentRangeHeader()) + .And(_ => ThenTheMappedRequestHasContentDispositionHeader("inline")) + .And(_ => ThenTheMappedRequestHasContentSize("This is my content".Length)) + .And(_ => ThenTheContentHeadersAreNotAddedToNonContentHeaders()) + .BDDfy(); + } + + [Fact] + public void should_not_add_content_headers() + { + this.Given(_ => GivenTheInputRequestHasContent("This is my content")) + .And(_ => GivenTheContentTypeIs("application/json")) + .And(_ => GivenTheInputRequestHasMethod("POST")) + .And(_ => GivenTheInputRequestHasAValidUri()) + .When(_ => WhenMapped()) + .Then(_ => ThenNoErrorIsReturned()) + .And(_ => ThenTheMappedRequestHasContentTypeHeader("application/json")) + .And(_ => ThenTheMappedRequestHasContentSize("This is my content".Length)) + .And(_ => ThenTheOtherContentTypeHeadersAreNotMapped()) + .BDDfy(); + } + + private void ThenTheContentHeadersAreNotAddedToNonContentHeaders() + { + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-Disposition"); + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentMD5"); + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentRange"); + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentLanguage"); + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentEncoding"); + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-ContentLocation"); + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-Length"); + _mappedRequest.Data.Headers.ShouldNotContain(x => x.Key == "Content-Type"); + } + + private void ThenTheOtherContentTypeHeadersAreNotMapped() + { + _mappedRequest.Data.Content.Headers.ContentDisposition.ShouldBeNull(); + _mappedRequest.Data.Content.Headers.ContentMD5.ShouldBeNull(); + _mappedRequest.Data.Content.Headers.ContentRange.ShouldBeNull(); + _mappedRequest.Data.Content.Headers.ContentLanguage.ShouldBeEmpty(); + _mappedRequest.Data.Content.Headers.ContentEncoding.ShouldBeEmpty(); + _mappedRequest.Data.Content.Headers.ContentLocation.ShouldBeNull(); + } + + private void ThenTheMappedRequestHasContentDispositionHeader(string expected) + { + _mappedRequest.Data.Content.Headers.ContentDisposition.DispositionType.ShouldBe(expected); + } + + private void GivenTheContentDispositionIs(string input) + { + _inputRequest.Headers.Add("Content-Disposition", input); + } + + private void ThenTheMappedRequestHasContentMD5Header(byte[] expected) + { + _mappedRequest.Data.Content.Headers.ContentMD5.ShouldBe(expected); + } + + private void GivenTheContentMD5Is(byte[] input) + { + var base64 = Convert.ToBase64String(input); + _inputRequest.Headers.Add("Content-MD5", base64); + } + + private void ThenTheMappedRequestHasContentRangeHeader() + { + _mappedRequest.Data.Content.Headers.ContentRange.From.ShouldBe(1); + _mappedRequest.Data.Content.Headers.ContentRange.To.ShouldBe(2); + } + + private void GivenTheContentRangeIs(string input) + { + _inputRequest.Headers.Add("Content-Range", input); + } + + private void ThenTheMappedRequestHasContentLocationHeader(string expected) + { + _mappedRequest.Data.Content.Headers.ContentLocation.OriginalString.ShouldBe(expected); + } + + private void GivenTheContentLocationIs(string input) + { + _inputRequest.Headers.Add("Content-Location", input); + } + + private void ThenTheMappedRequestHasContentLanguageHeader(string expected) + { + _mappedRequest.Data.Content.Headers.ContentLanguage.First().ShouldBe(expected); + } + + private void GivenTheContentLanguageIs(string input) + { + _inputRequest.Headers.Add("Content-Language", input); + } + + private void ThenTheMappedRequestHasContentEncodingHeader(string expected, string expectedTwo) + { + _mappedRequest.Data.Content.Headers.ContentEncoding.ToArray()[0].ShouldBe(expected); + _mappedRequest.Data.Content.Headers.ContentEncoding.ToArray()[1].ShouldBe(expectedTwo); + } + + private void GivenTheContentEncodingIs(string input) + { + _inputRequest.Headers.Add("Content-Encoding", input); + } + + private void GivenTheContentTypeIs(string contentType) + { + _inputRequest.ContentType = contentType; + } + + private void ThenTheMappedRequestHasContentTypeHeader(string expected) + { + _mappedRequest.Data.Content.Headers.ContentType.MediaType.ShouldBe(expected); + } + + private void ThenTheMappedRequestHasContentSize(long expected) + { + _mappedRequest.Data.Content.Headers.ContentLength.ShouldBe(expected); + } + + private void GivenTheInputRequestHasMethod(string method) + { + _inputRequest.Method = method; + } + + private void GivenTheInputRequestHasScheme(string scheme) + { + _inputRequest.Scheme = scheme; + } + + private void GivenTheInputRequestHasHost(string host) + { + _inputRequest.Host = new HostString(host); + } + + private void GivenTheInputRequestHasPath(string path) + { + if (path != null) + { + _inputRequest.Path = path; + } + } + + private void GivenTheInputRequestHasQueryString(string querystring) + { + if (querystring != null) + { + _inputRequest.QueryString = new QueryString(querystring); + } + } + + private void GivenTheInputRequestHasAValidUri() + { + GivenTheInputRequestHasScheme("http"); + GivenTheInputRequestHasHost("www.google.com"); + } + + private void GivenTheInputRequestHasHeaders() + { + _inputHeaders = new List>() + { + new KeyValuePair("abc", new StringValues(new string[]{"123","456" })), + new KeyValuePair("def", new StringValues(new string[]{"789","012" })), + }; + + foreach (var inputHeader in _inputHeaders) + { + _inputRequest.Headers.Add(inputHeader); + } + } + + private void GivenTheInputRequestHasNoHeaders() + { + _inputRequest.Headers.Clear(); + } + + private void GivenTheInputRequestHasContent(string content) + { + _inputRequest.Body = new MemoryStream(Encoding.UTF8.GetBytes(content)); + } + + private void GivenTheInputRequestHasNullContent() + { + _inputRequest.Body = null; + } + + private async Task WhenMapped() + { + _mappedRequest = await _requestMapper.Map(_inputRequest); + } + + private void ThenNoErrorIsReturned() + { + _mappedRequest.IsError.ShouldBeFalse(); + } + + private void ThenAnErrorIsReturned() + { + _mappedRequest.IsError.ShouldBeTrue(); + } + + private void ThenTheMappedRequestHasUri(string expectedUri) + { + _mappedRequest.Data.RequestUri.OriginalString.ShouldBe(expectedUri); + } + + private void ThenTheMappedRequestHasMethod(string expectedMethod) + { + _mappedRequest.Data.Method.ToString().ShouldBe(expectedMethod); + } + + private void ThenTheMappedRequestHasEachHeader() + { + _mappedRequest.Data.Headers.Count().ShouldBe(_inputHeaders.Count); + foreach(var header in _mappedRequest.Data.Headers) + { + var inputHeader = _inputHeaders.First(h => h.Key == header.Key); + inputHeader.ShouldNotBeNull(); + inputHeader.Value.Count().ShouldBe(header.Value.Count()); + foreach(var inputHeaderValue in inputHeader.Value) + { + header.Value.Any(v => v == inputHeaderValue); + } + } + } + + private void ThenTheMappedRequestHasNoHeaders() + { + _mappedRequest.Data.Headers.Count().ShouldBe(0); + } + + private void ThenTheMappedRequestHasContent(string expectedContent) + { + _mappedRequest.Data.Content.ReadAsStringAsync().GetAwaiter().GetResult().ShouldBe(expectedContent); + } + + private void ThenTheMappedRequestHasNoContent() + { + _mappedRequest.Data.Content.ShouldBeNull(); + } + + private void ThenTheMappedRequestIsNull() + { + _mappedRequest.Data.ShouldBeNull(); + } + } +} diff --git a/test/Ocelot.UnitTests/Requester/TracingHandlerFactoryTests.cs b/test/Ocelot.UnitTests/Requester/TracingHandlerFactoryTests.cs index bd633cb3..146af701 100644 --- a/test/Ocelot.UnitTests/Requester/TracingHandlerFactoryTests.cs +++ b/test/Ocelot.UnitTests/Requester/TracingHandlerFactoryTests.cs @@ -1,23 +1,30 @@ -using Butterfly.Client.Tracing; -using Moq; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Requester; -using Shouldly; -using Xunit; - namespace Ocelot.UnitTests.Requester { + using System; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Infrastructure.RequestData; + using Ocelot.Requester; + using Shouldly; + using Xunit; + using Ocelot.Logging; + public class TracingHandlerFactoryTests { - private TracingHandlerFactory _factory; - private Mock _tracer; + private readonly TracingHandlerFactory _factory; + private Mock _tracer; + private IServiceCollection _serviceCollection; + private IServiceProvider _serviceProvider; private Mock _repo; public TracingHandlerFactoryTests() { - _tracer = new Mock(); + _tracer = new Mock(); + _serviceCollection = new ServiceCollection(); + _serviceCollection.AddSingleton(_tracer.Object); + _serviceProvider = _serviceCollection.BuildServiceProvider(); _repo = new Mock(); - _factory = new TracingHandlerFactory(_tracer.Object, _repo.Object); + _factory = new TracingHandlerFactory(_serviceProvider, _repo.Object); } [Fact] @@ -27,4 +34,4 @@ namespace Ocelot.UnitTests.Requester handler.ShouldBeOfType(); } } -} \ No newline at end of file +} diff --git a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs index fbc17868..9460300b 100644 --- a/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs +++ b/test/Ocelot.UnitTests/Responder/ErrorsToHttpStatusCodeMapperTests.cs @@ -72,7 +72,6 @@ namespace Ocelot.UnitTests.Responder [InlineData(OcelotErrorCode.UnableToFindLoadBalancerError)] [InlineData(OcelotErrorCode.UnableToFindServiceDiscoveryProviderError)] [InlineData(OcelotErrorCode.UnableToFindQoSProviderError)] - [InlineData(OcelotErrorCode.UnableToSetConfigInConsulError)] [InlineData(OcelotErrorCode.UnknownError)] [InlineData(OcelotErrorCode.UnmappableRequestError)] [InlineData(OcelotErrorCode.UnsupportedAuthenticationProviderError)] @@ -126,7 +125,7 @@ namespace Ocelot.UnitTests.Responder // If this test fails then it's because the number of error codes has changed. // You should make the appropriate changes to the test cases here to ensure // they cover all the error codes, and then modify this assertion. - Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(35, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?"); + Enum.GetNames(typeof(OcelotErrorCode)).Length.ShouldBe(34, "Looks like the number of error codes has changed. Do you need to modify ErrorsToHttpStatusCodeMapper?"); } private void ShouldMapErrorToStatusCode(OcelotErrorCode errorCode, HttpStatusCode expectedHttpStatusCode) diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ConsulServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ConsulServiceDiscoveryProviderTests.cs deleted file mode 100644 index b60330d8..00000000 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ConsulServiceDiscoveryProviderTests.cs +++ /dev/null @@ -1,249 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Consul; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Moq; -using Ocelot.Infrastructure.Consul; -using Ocelot.Logging; -using Ocelot.ServiceDiscovery.Configuration; -using Ocelot.ServiceDiscovery.Providers; -using Ocelot.Values; -using Xunit; -using TestStack.BDDfy; -using Shouldly; - -namespace Ocelot.UnitTests.ServiceDiscovery -{ - public class ConsulServiceDiscoveryProviderTests : IDisposable - { - private IWebHost _fakeConsulBuilder; - private readonly List _serviceEntries; - private ConsulServiceDiscoveryProvider _provider; - private readonly string _serviceName; - private readonly int _port; - private readonly string _consulHost; - private readonly string _fakeConsulServiceDiscoveryUrl; - private List _services; - private readonly Mock _factory; - private readonly Mock _logger; - private string _receivedToken; - private IConsulClientFactory _clientFactory; - - public ConsulServiceDiscoveryProviderTests() - { - _serviceName = "test"; - _port = 8500; - _consulHost = "localhost"; - _fakeConsulServiceDiscoveryUrl = $"http://{_consulHost}:{_port}"; - _serviceEntries = new List(); - - _factory = new Mock(); - _clientFactory = new ConsulClientFactory(); - _logger = new Mock(); - _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - - var config = new ConsulRegistryConfiguration(_consulHost, _port, _serviceName, null); - _provider = new ConsulServiceDiscoveryProvider(config, _factory.Object, _clientFactory); - } - - [Fact] - public void should_return_service_from_consul() - { - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = _serviceName, - Address = "localhost", - Port = 50881, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - - this.Given(x =>GivenThereIsAFakeConsulServiceDiscoveryProvider(_fakeConsulServiceDiscoveryUrl, _serviceName)) - .And(x => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne)) - .When(x => WhenIGetTheServices()) - .Then(x => ThenTheCountIs(1)) - .BDDfy(); - } - - [Fact] - public void should_use_token() - { - var token = "test token"; - var config = new ConsulRegistryConfiguration(_consulHost, _port, _serviceName, token); - _provider = new ConsulServiceDiscoveryProvider(config, _factory.Object, _clientFactory); - - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = _serviceName, - Address = "localhost", - Port = 50881, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - - this.Given(_ => GivenThereIsAFakeConsulServiceDiscoveryProvider(_fakeConsulServiceDiscoveryUrl, _serviceName)) - .And(_ => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne)) - .When(_ => WhenIGetTheServices()) - .Then(_ => ThenTheCountIs(1)) - .And(_ => _receivedToken.ShouldBe(token)) - .BDDfy(); - } - - [Fact] - public void should_not_return_services_with_invalid_address() - { - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = _serviceName, - Address = "http://localhost", - Port = 50881, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - - var serviceEntryTwo = new ServiceEntry() - { - Service = new AgentService() - { - Service = _serviceName, - Address = "http://localhost", - Port = 50888, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - - this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(_fakeConsulServiceDiscoveryUrl, _serviceName)) - .And(x => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo)) - .When(x => WhenIGetTheServices()) - .Then(x => ThenTheCountIs(0)) - .And(x => ThenTheLoggerHasBeenCalledCorrectlyForInvalidAddress()) - .BDDfy(); - } - - [Fact] - public void should_not_return_services_with_invalid_port() - { - var serviceEntryOne = new ServiceEntry() - { - Service = new AgentService() - { - Service = _serviceName, - Address = "localhost", - Port = -1, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - - var serviceEntryTwo = new ServiceEntry() - { - Service = new AgentService() - { - Service = _serviceName, - Address = "localhost", - Port = 0, - ID = Guid.NewGuid().ToString(), - Tags = new string[0] - }, - }; - - this.Given(x => GivenThereIsAFakeConsulServiceDiscoveryProvider(_fakeConsulServiceDiscoveryUrl, _serviceName)) - .And(x => GivenTheServicesAreRegisteredWithConsul(serviceEntryOne, serviceEntryTwo)) - .When(x => WhenIGetTheServices()) - .Then(x => ThenTheCountIs(0)) - .And(x => ThenTheLoggerHasBeenCalledCorrectlyForInvalidPorts()) - .BDDfy(); - } - - private void ThenTheLoggerHasBeenCalledCorrectlyForInvalidAddress() - { - _logger.Verify( - x => x.LogWarning( - "Unable to use service Address: http://localhost and Port: 50881 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"), - Times.Once); - - _logger.Verify( - x => x.LogWarning( - "Unable to use service Address: http://localhost and Port: 50888 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"), - Times.Once); - } - - private void ThenTheLoggerHasBeenCalledCorrectlyForInvalidPorts() - { - _logger.Verify( - x => x.LogWarning( - "Unable to use service Address: localhost and Port: -1 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"), - Times.Once); - - _logger.Verify( - x => x.LogWarning( - "Unable to use service Address: localhost and Port: 0 as it is invalid. Address must contain host only e.g. localhost and port must be greater than 0"), - Times.Once); - } - - private void ThenTheCountIs(int count) - { - _services.Count.ShouldBe(count); - } - - private void WhenIGetTheServices() - { - _services = _provider.Get().GetAwaiter().GetResult(); - } - - private void GivenTheServicesAreRegisteredWithConsul(params ServiceEntry[] serviceEntries) - { - foreach (var serviceEntry in serviceEntries) - { - _serviceEntries.Add(serviceEntry); - } - } - - private void GivenThereIsAFakeConsulServiceDiscoveryProvider(string url, string serviceName) - { - _fakeConsulBuilder = new WebHostBuilder() - .UseUrls(url) - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .UseUrls(url) - .Configure(app => - { - app.Run(async context => - { - if (context.Request.Path.Value == $"/v1/health/service/{serviceName}") - { - if (context.Request.Headers.TryGetValue("X-Consul-Token", out var values)) - { - _receivedToken = values.First(); - } - - await context.Response.WriteJsonAsync(_serviceEntries); - } - }); - }) - .Build(); - - _fakeConsulBuilder.Start(); - } - - public void Dispose() - { - _fakeConsulBuilder?.Dispose(); - } - } -} diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/EurekaServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/EurekaServiceDiscoveryProviderTests.cs deleted file mode 100644 index adfb6957..00000000 --- a/test/Ocelot.UnitTests/ServiceDiscovery/EurekaServiceDiscoveryProviderTests.cs +++ /dev/null @@ -1,118 +0,0 @@ -namespace Ocelot.UnitTests.ServiceDiscovery -{ - using System; - using System.Collections.Generic; - using System.Threading.Tasks; - using Moq; - using Ocelot.ServiceDiscovery.Providers; - using Pivotal.Discovery.Client; - using Shouldly; - using Steeltoe.Common.Discovery; - using TestStack.BDDfy; - using Values; - using Xunit; - - public class EurekaServiceDiscoveryProviderTests - { - private readonly EurekaServiceDiscoveryProvider _provider; - private readonly Mock _client; - private readonly string _serviceId; - private List _instances; - private List _result; - - public EurekaServiceDiscoveryProviderTests() - { - _serviceId = "Laura"; - _client = new Mock(); - _provider = new EurekaServiceDiscoveryProvider(_serviceId, _client.Object); - } - - [Fact] - public void should_return_empty_services() - { - this.When(_ => WhenIGet()) - .Then(_ => ThenTheCountIs(0)) - .BDDfy(); - } - - [Fact] - public void should_return_service_from_client() - { - var instances = new List - { - new EurekaService(_serviceId, "somehost", 801, false, new Uri("http://somehost:801"), new Dictionary()) - }; - - this.Given(_ => GivenThe(instances)) - .When(_ => WhenIGet()) - .Then(_ => ThenTheCountIs(1)) - .And(_ => ThenTheClientIsCalledCorrectly()) - .And(_ => ThenTheServiceIsMapped()) - .BDDfy(); - } - - [Fact] - public void should_return_services_from_client() - { - var instances = new List - { - new EurekaService(_serviceId, "somehost", 801, false, new Uri("http://somehost:801"), new Dictionary()), - new EurekaService(_serviceId, "somehost", 801, false, new Uri("http://somehost:801"), new Dictionary()) - }; - - this.Given(_ => GivenThe(instances)) - .When(_ => WhenIGet()) - .Then(_ => ThenTheCountIs(2)) - .And(_ => ThenTheClientIsCalledCorrectly()) - .BDDfy(); - } - - private void ThenTheServiceIsMapped() - { - _result[0].HostAndPort.DownstreamHost.ShouldBe("somehost"); - _result[0].HostAndPort.DownstreamPort.ShouldBe(801); - _result[0].Name.ShouldBe(_serviceId); - } - - private void ThenTheCountIs(int expected) - { - _result.Count.ShouldBe(expected); - } - - private void ThenTheClientIsCalledCorrectly() - { - _client.Verify(x => x.GetInstances(_serviceId), Times.Once); - } - - private async Task WhenIGet() - { - _result = await _provider.Get(); - } - - private void GivenThe(List instances) - { - _instances = instances; - _client.Setup(x => x.GetInstances(It.IsAny())).Returns(instances); - } - } - - public class EurekaService : IServiceInstance - { - public EurekaService(string serviceId, string host, int port, bool isSecure, Uri uri, IDictionary metadata) - { - ServiceId = serviceId; - Host = host; - Port = port; - IsSecure = isSecure; - Uri = uri; - Metadata = metadata; - } - - public string ServiceId { get; } - public string Host { get; } - public int Port { get; } - public bool IsSecure { get; } - public Uri Uri { get; } - public IDictionary Metadata { get; } - } -} diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/PollingConsulServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/PollingConsulServiceDiscoveryProviderTests.cs deleted file mode 100644 index 86d523e2..00000000 --- a/test/Ocelot.UnitTests/ServiceDiscovery/PollingConsulServiceDiscoveryProviderTests.cs +++ /dev/null @@ -1,80 +0,0 @@ -namespace Ocelot.UnitTests.ServiceDiscovery -{ - using System; - using System.Collections.Generic; - using Moq; - using Ocelot.Logging; - using Ocelot.ServiceDiscovery.Providers; - using Ocelot.Values; - using Xunit; - using TestStack.BDDfy; - using Shouldly; - using static Ocelot.Infrastructure.Wait; - - public class PollingConsulServiceDiscoveryProviderTests - { - private readonly int _delay; - private PollingConsulServiceDiscoveryProvider _provider; - private readonly List _services; - private readonly Mock _factory; - private readonly Mock _logger; - private readonly Mock _consulServiceDiscoveryProvider; - private List _result; - - public PollingConsulServiceDiscoveryProviderTests() - { - _services = new List(); - _delay = 1; - _factory = new Mock(); - _logger = new Mock(); - _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _consulServiceDiscoveryProvider = new Mock(); - } - - [Fact] - public void should_return_service_from_consul() - { - var service = new Service("", new ServiceHostAndPort("", 0), "", "", new List()); - - this.Given(x => GivenConsulReturns(service)) - .When(x => WhenIGetTheServices(1)) - .Then(x => ThenTheCountIs(1)) - .BDDfy(); - } - - private void GivenConsulReturns(Service service) - { - _services.Add(service); - _consulServiceDiscoveryProvider.Setup(x => x.Get()).ReturnsAsync(_services); - } - - private void ThenTheCountIs(int count) - { - _result.Count.ShouldBe(count); - } - - private void WhenIGetTheServices(int expected) - { - _provider = new PollingConsulServiceDiscoveryProvider(_delay, "", _factory.Object, _consulServiceDiscoveryProvider.Object); - - var result = WaitFor(3000).Until(() => { - try - { - _result = _provider.Get().GetAwaiter().GetResult(); - if(_result.Count == expected) - { - return true; - } - - return false; - } - catch(Exception) - { - return false; - } - }); - - result.ShouldBeTrue(); - } - } -} diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceFabricServiceDiscoveryProviderTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceFabricServiceDiscoveryProviderTests.cs index eabe72d7..a9020e3e 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceFabricServiceDiscoveryProviderTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceFabricServiceDiscoveryProviderTests.cs @@ -1,23 +1,12 @@ -using Ocelot.ServiceDiscovery.Configuration; -using Ocelot.ServiceDiscovery.Providers; - -namespace Ocelot.UnitTests.ServiceDiscovery +namespace Ocelot.UnitTests.ServiceDiscovery { - using System; using System.Collections.Generic; - using System.IO; - using System.Text; - using Consul; - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.Http; - using Moq; - using Ocelot.Logging; - using Ocelot.ServiceDiscovery; using Ocelot.Values; using Xunit; using TestStack.BDDfy; using Shouldly; + using Ocelot.ServiceDiscovery.Configuration; + using Ocelot.ServiceDiscovery.Providers; public class ServiceFabricServiceDiscoveryProviderTests { diff --git a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs index 9ef05f3c..6db38012 100644 --- a/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs +++ b/test/Ocelot.UnitTests/ServiceDiscovery/ServiceProviderFactoryTests.cs @@ -1,39 +1,38 @@ -using System; -using System.Collections.Generic; -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Infrastructure.Consul; -using Ocelot.Logging; -using Ocelot.ServiceDiscovery; -using Ocelot.ServiceDiscovery.Providers; -using Shouldly; -using TestStack.BDDfy; -using Xunit; - namespace Ocelot.UnitTests.ServiceDiscovery { - using Pivotal.Discovery.Client; - using Steeltoe.Common.Discovery; + using System; + using System.Threading.Tasks; + using Microsoft.Extensions.DependencyInjection; + using Values; + using System.Collections.Generic; + using Moq; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.Logging; + using Ocelot.ServiceDiscovery; + using Ocelot.ServiceDiscovery.Providers; + using Shouldly; + using TestStack.BDDfy; + using Xunit; public class ServiceProviderFactoryTests { private ServiceProviderConfiguration _serviceConfig; private IServiceDiscoveryProvider _result; - private readonly ServiceDiscoveryProviderFactory _factory; + private ServiceDiscoveryProviderFactory _factory; private DownstreamReRoute _reRoute; - private Mock _loggerFactory; - private Mock _discoveryClient; + private readonly Mock _loggerFactory; private Mock _logger; + private IServiceProvider _provider; + private readonly IServiceCollection _collection; public ServiceProviderFactoryTests() { _loggerFactory = new Mock(); _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _discoveryClient = new Mock(); - var consulClient = new Mock(); - _factory = new ServiceDiscoveryProviderFactory(_loggerFactory.Object, consulClient.Object, _discoveryClient.Object); + _collection = new ServiceCollection(); + _provider = _collection.BuildServiceProvider(); + _factory = new ServiceDiscoveryProviderFactory(_loggerFactory.Object, _provider); } [Fact] @@ -72,7 +71,7 @@ namespace Ocelot.UnitTests.ServiceDiscovery } [Fact] - public void should_return_consul_service_provider() + public void should_call_delegate() { var reRoute = new DownstreamReRouteBuilder() .WithServiceName("product") @@ -83,27 +82,9 @@ namespace Ocelot.UnitTests.ServiceDiscovery .Build(); this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute)) + .And(x => GivenAFakeDelegate()) .When(x => x.WhenIGetTheServiceProvider()) - .Then(x => x.ThenTheServiceProviderIs()) - .BDDfy(); - } - - [Fact] - public void should_return_polling_consul_service_provider() - { - var reRoute = new DownstreamReRouteBuilder() - .WithServiceName("product") - .WithUseServiceDiscovery(true) - .Build(); - - var serviceConfig = new ServiceProviderConfigurationBuilder() - .WithType("PollConsul") - .WithPollingInterval(100000) - .Build(); - - this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute)) - .When(x => x.WhenIGetTheServiceProvider()) - .Then(x => x.ThenTheServiceProviderIs()) + .Then(x => x.ThenTheDelegateIsCalled()) .BDDfy(); } @@ -125,22 +106,25 @@ namespace Ocelot.UnitTests.ServiceDiscovery .BDDfy(); } - [Fact] - public void should_return_eureka_provider() + private void GivenAFakeDelegate() { - var reRoute = new DownstreamReRouteBuilder() - .WithServiceName("product") - .WithUseServiceDiscovery(true) - .Build(); + ServiceDiscoveryFinderDelegate fake = (provider, config, name) => new Fake(); + _collection.AddSingleton(fake); + _provider = _collection.BuildServiceProvider(); + _factory = new ServiceDiscoveryProviderFactory(_loggerFactory.Object, _provider); + } - var serviceConfig = new ServiceProviderConfigurationBuilder() - .WithType("Eureka") - .Build(); + class Fake : IServiceDiscoveryProvider + { + public Task> Get() + { + return null; + } + } - this.Given(x => x.GivenTheReRoute(serviceConfig, reRoute)) - .When(x => x.WhenIGetTheServiceProvider()) - .Then(x => x.ThenTheServiceProviderIs()) - .BDDfy(); + private void ThenTheDelegateIsCalled() + { + _result.GetType().Name.ShouldBe("Fake"); } private void ThenTheFollowingServicesAreReturned(List downstreamAddresses)