diff --git a/.editorconfig b/.editorconfig index 82435303..6aa3d30f 100644 --- a/.editorconfig +++ b/.editorconfig @@ -4,6 +4,7 @@ root = true end_of_line = lf insert_final_newline = true -[*.cs] +[*.cs] +end_of_line = lf indent_style = space indent_size = 4 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..167f8eda --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# 2010 +*.txt -crlf + +# 2020 +*.txt text eol=lf \ No newline at end of file diff --git a/Ocelot.sln b/Ocelot.sln index 38405abf..203dfb92 100644 --- a/Ocelot.sln +++ b/Ocelot.sln @@ -8,6 +8,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{3FA7C349-DBE8-4904-A2CE-015B8869CE6C}" ProjectSection(SolutionItems) = preProject .dockerignore = .dockerignore + .editorconfig = .editorconfig .gitignore = .gitignore build.cake = build.cake build.ps1 = build.ps1 diff --git a/README.md b/README.md index f6ff9b57..a18d15fe 100644 --- a/README.md +++ b/README.md @@ -8,28 +8,16 @@ # Ocelot -Ocelot is a .NET API Gateway. This project is aimed at people using .NET running -a micro services / service oriented architecture +Ocelot is a .NET API Gateway. This project is aimed at people using .NET running a micro services / service oriented architecture that need a unified point of entry into their system. However it will work with anything that speaks HTTP and run on any platform that ASP.NET Core supports. -In particular I want easy integration with -IdentityServer reference and bearer tokens. +In particular I want easy integration with IdentityServer reference and bearer tokens. -We have been unable to find this in my current workplace -without having to write our own Javascript middlewares -to handle the IdentityServer reference tokens. We would -rather use the IdentityServer code that already exists -to do this. +We have been unable to find this in my current workplacewithout having to write our own Javascript middlewares to handle the IdentityServer reference tokens. We would rather use the IdentityServer code that already existsto do this. Ocelot is a bunch of middlewares in a specific order. -Ocelot manipulates the HttpRequest object into a state specified by its configuration until -it reaches a request builder middleware where it creates a HttpRequestMessage object which is -used to make a request to a downstream service. The middleware that makes the request is -the last thing in the Ocelot pipeline. It does not call the next middleware. -The response from the downstream service is retrieved as the requests goes back up the Ocelot pipeline. -There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that -is returned to the client. That is basically it with a bunch of other features! +Ocelot manipulates the HttpRequest object into a state specified by its configuration until it reaches a request builder middleware where it creates a HttpRequestMessage object which is used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. The response from the downstream service is retrieved as the requests goes back up the Ocelot pipeline. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features! ## Features @@ -81,15 +69,13 @@ We love to receive contributions from the community so please keep them coming : Pull requests, issues and commentary welcome! -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 :) +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 contribute for the first time I suggest looking at a help wanted & small effort issue :) ## Donate -If you think this project is worth supporting financially please make a contribution using the button below! +If you think this project is worth supporting financially please make a contribution using the button below! We use the money to run the https://threemammals.com website. [![Support via PayPal](https://cdn.rawgit.com/twolfson/paypal-github-button/1.0.0/dist/button.svg)](https://www.paypal.me/ThreeMammals/) diff --git a/docs/features/administration.rst b/docs/features/administration.rst index 4d891994..274e7299 100644 --- a/docs/features/administration.rst +++ b/docs/features/administration.rst @@ -1,140 +1,128 @@ -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). Please note if you are using something like docker to host Ocelot it might not be able to -call back to localhost etc and you need to know what you are doing with docker networking in this scenario. Anyway 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. +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 yourinitial Startup.cs. + +The path can be anything you want and it is obviously reccomended don't usea url you would like to route through with Ocelot as this will not work. The administration uses theMapWhen 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). Please note if you are using something like docker to host Ocelot it might not be able to call back to localhost etc and you need to know what you are doing with docker networking in this scenario. Anyway 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 scriptscalled 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 thecluster 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/authentication.rst b/docs/features/authentication.rst index 641ca2ed..8e0abd7b 100644 --- a/docs/features/authentication.rst +++ b/docs/features/authentication.rst @@ -1,182 +1,179 @@ -Authentication -============== - -In order to authenticate ReRoutes and subsequently use any of Ocelot's claims based features such as authorisation or modifying the request with values from the token. Users must register authentication services in their Startup.cs as usual but they provide a scheme (authentication provider key) with each registration e.g. - -.. code-block:: csharp - - public void ConfigureServices(IServiceCollection services) - { - var authenticationProviderKey = "TestKey"; - - services.AddAuthentication() - .AddJwtBearer(authenticationProviderKey, x => - { - }); - } - - -In this example TestKey is the scheme that this provider has been registered with. -We then map this to a ReRoute in the configuration e.g. - -.. code-block:: json - - "ReRoutes": [{ - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 51876, - } - ], - "DownstreamPathTemplate": "/", - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": ["Post"], - "ReRouteIsCaseSensitive": false, - "DownstreamScheme": "http", - "AuthenticationOptions": { - "AuthenticationProviderKey": "TestKey", - "AllowedScopes": [] - } - }] - -When Ocelot runs it will look at this ReRoutes AuthenticationOptions.AuthenticationProviderKey -and check that there is an Authentication provider registered with the given key. If there isn't then Ocelot -will not start up, if there is then the ReRoute will use that provider when it executes. - -If a ReRoute is authenticated Ocelot will invoke whatever scheme is associated with it while executing the authentication middleware. If the request fails authentication Ocelot returns a http status code 401. - -JWT Tokens -^^^^^^^^^^ - -If you want to authenticate using JWT tokens maybe from a provider like Auth0 you can register your authentication middleware as normal e.g. - -.. code-block:: csharp - - public void ConfigureServices(IServiceCollection services) - { - var authenticationProviderKey = "TestKey"; - - services.AddAuthentication() - .AddJwtBearer(authenticationProviderKey, x => - { - x.Authority = "test"; - x.Audience = "test"; - }); - - services.AddOcelot(); - } - -Then map the authentication provider key to a ReRoute in your configuration e.g. - -.. code-block:: json - - "ReRoutes": [{ - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 51876, - } - ], - "DownstreamPathTemplate": "/", - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": ["Post"], - "ReRouteIsCaseSensitive": false, - "DownstreamScheme": "http", - "AuthenticationOptions": { - "AuthenticationProviderKey": "TestKey", - "AllowedScopes": [] - } - }] - - - -Identity Server Bearer Tokens -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -In order to use IdentityServer bearer tokens, register your IdentityServer services as usual in ConfigureServices with a scheme (key). If you don't understand how to do this please consult the IdentityServer documentation. - -.. code-block:: csharp - - public void ConfigureServices(IServiceCollection services) - { - var authenticationProviderKey = "TestKey"; - Action options = o => - { - o.Authority = "https://whereyouridentityserverlives.com"; - o.ApiName = "api"; - o.SupportedTokens = SupportedTokens.Both; - o.ApiSecret = "secret"; - }; - - services.AddAuthentication() - .AddIdentityServerAuthentication(authenticationProviderKey, options); - - services.AddOcelot(); - } - -Then map the authentication provider key to a ReRoute in your configuration e.g. - -.. code-block:: json - - "ReRoutes": [{ - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 51876, - } - ], - "DownstreamPathTemplate": "/", - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": ["Post"], - "ReRouteIsCaseSensitive": false, - "DownstreamScheme": "http", - "AuthenticationOptions": { - "AuthenticationProviderKey": "TestKey", - "AllowedScopes": [] - } - }] - -Okta -^^^^ -Add the following to your startup Configure method: - -.. code-block:: csharp - - app - .UseAuthentication() - .UseOcelot() - .Wait(); - - -Add the following, at minimum, to your startup ConfigureServices method: - -.. code-block:: csharp - - services - .AddAuthentication() - .AddJwtBearer(oktaProviderKey, options => - { - options.Audience = configuration["Authentication:Okta:Audience"]; // Okta Authorization server Audience - options.Authority = configuration["Authentication:Okta:Server"]; // Okta Authorization Issuer URI URL e.g. https://{subdomain}.okta.com/oauth2/{authidentifier} - }); - services.AddOcelot(configuration); - - -NOTE: In order to get Ocelot to view the scope claim from Okta properly, you have to add the following to map the default Okta "scp" claim to "scope" - - -.. code-block:: csharp - - // Map Okta scp to scope claims instead of http://schemas.microsoft.com/identity/claims/scope to allow ocelot to read/verify them - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("scp"); - JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("scp", "scope"); - - -`Issue 446 `_ that contains some code and examples that might help with Okta integration. - -Allowed Scopes -^^^^^^^^^^^^^ - -If you add scopes to AllowedScopes Ocelot will get all the user claims (from the token) of the type scope and make sure that the user has all of the scopes in the list. - -This is a way to restrict access to a ReRoute on a per scope basis. +Authentication +============== + +In order to authenticate ReRoutes and subsequently use any of Ocelot's claims based features such as authorisation or modifying the request with values from the token. Users must register authentication services in their Startup.cs as usual but they provide a scheme (authentication provider key) with each registration e.g. + +.. code-block:: csharp + + public void ConfigureServices(IServiceCollection services) + { + var authenticationProviderKey = "TestKey"; + + services.AddAuthentication() + .AddJwtBearer(authenticationProviderKey, x => + { + }); + } + + +In this example TestKey is the scheme that this provider has been registered with. We then map this to a ReRoute in the configuration e.g. + +.. code-block:: json + + "ReRoutes": [{ + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 51876, + } + ], + "DownstreamPathTemplate": "/", + "UpstreamPathTemplate": "/", + "UpstreamHttpMethod": ["Post"], + "ReRouteIsCaseSensitive": false, + "DownstreamScheme": "http", + "AuthenticationOptions": { + "AuthenticationProviderKey": "TestKey", + "AllowedScopes": [] + } + }] + +When Ocelot runs it will look at this ReRoutes AuthenticationOptions.AuthenticationProviderKey and check that there is an Authentication provider registered with the given key. If there isn't then Ocelot will not start up, if there is then the ReRoute will use that provider when it executes. + +If a ReRoute is authenticated Ocelot will invoke whatever scheme is associated with it while executing the authentication middleware. If the request fails authentication Ocelot returns a http status code 401. + +JWT Tokens +^^^^^^^^^^ + +If you want to authenticate using JWT tokens maybe from a provider like Auth0 you can register your authentication middleware as normal e.g. + +.. code-block:: csharp + + public void ConfigureServices(IServiceCollection services) + { + var authenticationProviderKey = "TestKey"; + + services.AddAuthentication() + .AddJwtBearer(authenticationProviderKey, x => + { + x.Authority = "test"; + x.Audience = "test"; + }); + + services.AddOcelot(); + } + +Then map the authentication provider key to a ReRoute in your configuration e.g. + +.. code-block:: json + + "ReRoutes": [{ + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 51876, + } + ], + "DownstreamPathTemplate": "/", + "UpstreamPathTemplate": "/", + "UpstreamHttpMethod": ["Post"], + "ReRouteIsCaseSensitive": false, + "DownstreamScheme": "http", + "AuthenticationOptions": { + "AuthenticationProviderKey": "TestKey", + "AllowedScopes": [] + } + }] + + + +Identity Server Bearer Tokens +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In order to use IdentityServer bearer tokens, register your IdentityServer services as usual in ConfigureServices with a scheme (key). If you don't understand how to do this please consult the IdentityServer documentation. + +.. code-block:: csharp + + public void ConfigureServices(IServiceCollection services) + { + var authenticationProviderKey = "TestKey"; + Action options = o => + { + o.Authority = "https://whereyouridentityserverlives.com"; + o.ApiName = "api"; + o.SupportedTokens = SupportedTokens.Both; + o.ApiSecret = "secret"; + }; + + services.AddAuthentication() + .AddIdentityServerAuthentication(authenticationProviderKey, options); + + services.AddOcelot(); + } + +Then map the authentication provider key to a ReRoute in your configuration e.g. + +.. code-block:: json + + "ReRoutes": [{ + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 51876, + } + ], + "DownstreamPathTemplate": "/", + "UpstreamPathTemplate": "/", + "UpstreamHttpMethod": ["Post"], + "ReRouteIsCaseSensitive": false, + "DownstreamScheme": "http", + "AuthenticationOptions": { + "AuthenticationProviderKey": "TestKey", + "AllowedScopes": [] + } + }] + +Okta +^^^^ +Add the following to your startup Configure method: + +.. code-block:: csharp + + app + .UseAuthentication() + .UseOcelot() + .Wait(); + + +Add the following, at minimum, to your startup ConfigureServices method: + +.. code-block:: csharp + + services + .AddAuthentication() + .AddJwtBearer(oktaProviderKey, options => + { + options.Audience = configuration["Authentication:Okta:Audience"]; // Okta Authorization server Audience + options.Authority = configuration["Authentication:Okta:Server"]; // Okta Authorization Issuer URI URL e.g. https://{subdomain}.okta.com/oauth2/{authidentifier} + }); + services.AddOcelot(configuration); + + +NOTE: In order to get Ocelot to view the scope claim from Okta properly, you have to add the following to map the default Okta "scp" claim to "scope" + + +.. code-block:: csharp + + // Map Okta scp to scope claims instead of http://schemas.microsoft.com/identity/claims/scope to allow ocelot to read/verify them + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("scp"); + JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Add("scp", "scope"); + + +`Issue 446 `_ that contains some code and examples that might help with Okta integration. + +Allowed Scopes +^^^^^^^^^^^^^ + +If you add scopes to AllowedScopes Ocelot will get all the user claims (from the token) of the type scope and make sure that the user has all of the scopes in the list. + +This is a way to restrict access to a ReRoute on a per scope basis. diff --git a/docs/features/authorisation.rst b/docs/features/authorisation.rst index b7a4ec1e..e19e03c4 100644 --- a/docs/features/authorisation.rst +++ b/docs/features/authorisation.rst @@ -1,8 +1,7 @@ Authorisation ============= -Ocelot supports claims based authorisation which is run post authentication. This means if -you have a route you want to authorise you can add the following to you ReRoute configuration. +Ocelot supports claims based authorisation which is run post authentication. This means if you have a route you want to authorise you can add the following to you ReRoute configuration. .. code-block:: json @@ -10,9 +9,7 @@ you have a route you want to authorise you can add the following to you ReRoute "UserType": "registered" } -In this example when the authorisation middleware is called Ocelot will check to see -if the user has the claim type UserType and if the value of that claim is registered. -If it isn't then the user will not be authorised and the response will be 403 forbidden. +In this example when the authorisation middleware is called Ocelot will check to seeif the user has the claim type UserType and if the value of that claim is registered. If it isn't then the user will not be authorised and the response will be 403 forbidden. diff --git a/docs/features/caching.rst b/docs/features/caching.rst index c4be74f4..79cb4051 100644 --- a/docs/features/caching.rst +++ b/docs/features/caching.rst @@ -1,10 +1,7 @@ Caching ======= -Ocelot supports some very rudimentary caching at the moment provider by -the `CacheManager `_ project. This is an amazing project -that is solving a lot of caching problems. I would reccomend using this package to -cache with Ocelot. +Ocelot supports some very rudimentary caching at the moment provider by the `CacheManager `_ project. This is an amazing project 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. @@ -32,19 +29,14 @@ Finally in order to use caching on a route in your ReRoute configuration add thi 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 AddCacheManager configuration method. You can use any settings supported by -the CacheManager package and just pass them in. +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. +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>()`` +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. diff --git a/docs/features/claimstransformation.rst b/docs/features/claimstransformation.rst index 8d401317..15ab0523 100644 --- a/docs/features/claimstransformation.rst +++ b/docs/features/claimstransformation.rst @@ -1,35 +1,17 @@ Claims Transformation ===================== -Ocelot allows the user to access claims and transform them into headers, query string -parameters, other claims and change downstream paths. This is only available once a user -has been authenticated. +Ocelot allows the user to access claims and transform them into headers, query string parameters, other claims and change downstream paths. This is only available once a user has been authenticated. -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, then -the claims to query string parameters middleware, and Finally the claims to downstream path -middleware. +After the user is authenticated we run the claims to claims transformation middleware. This allows the user to transform claims before the authorisation middleware is called. After the user is authorised first we call the claims to headers middleware, thenthe claims to query string parameters middleware, and Finally the claims to downstream pathmiddleware. -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, or ChangeDownstreamPathTemplate. +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, or ChangeDownstreamPathTemplate. 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 -or query parameter. In the case of ChangeDownstreamPathTemplate, the key must be -also specified in the DownstreamPathTemplate, in order to do the transformation. +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 or query parameter. In the case of ChangeDownstreamPathTemplate, the key must be also specified in the DownstreamPathTemplate, in order to do the transformation. -The value of the entry is parsed to logic that will perform the transform. First of -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 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. +The value of the entry is parsed to logic that will perform the transform. First ofall 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 an 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 Transformation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -43,8 +25,7 @@ Below is an example configuration that will transforms claims to claims "UserId": "Claims[sub] > value[1] > |" } -This shows a transforms where Ocelot looks at the users sub claim and transforms it into -UserType and UserId claims. Assuming the sub looks like this "usertypevalue|useridvalue". +This shows a transforms where Ocelot looks at the users sub claim and transforms it into UserType and UserId claims. Assuming the sub looks like this "usertypevalue|useridvalue". Claims to Headers Tranformation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -57,8 +38,7 @@ 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 transforms it into a -CustomerId header. Assuming the sub looks like this "usertypevalue|useridvalue". +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 Transformation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -71,8 +51,7 @@ 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 it as -a query string parameter to be forwarded onto the downstream service. +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. Claims to Downstream Path Transformation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -87,10 +66,7 @@ Below is an example configuration that will transform claims to downstream path "userId": "Claims[sub] > value[1] > |", } -This shows a transform where Ocelot looks at the users userId claim and substitutes the value -to the "{userId}" placeholder specified in the DownstreamPathTemplate. Take into account that the -key specified in the ChangeDownstreamPathTemplate must be the same than the placeholder specified in +This shows a transform where Ocelot looks at the users userId claim and substitutes the value to the "{userId}" placeholder specified in the DownstreamPathTemplate. Take into account that the key specified in the ChangeDownstreamPathTemplate must be the same than the placeholder specified in the DownstreamPathTemplate. -Note: if a key specified in the ChangeDownstreamPathTemplate does not exist as a placeholder in DownstreamPathTemplate -it will fail at runtime returning an error in the response. \ No newline at end of file +Note: if a key specified in the ChangeDownstreamPathTemplate does not exist as a placeholder in DownstreamPathTemplate it will fail at runtime returning an error in the response. \ No newline at end of file diff --git a/docs/features/configuration.rst b/docs/features/configuration.rst index ebd82203..f658d84d 100644 --- a/docs/features/configuration.rst +++ b/docs/features/configuration.rst @@ -1,287 +1,273 @@ -Configuration -============ - -An example configuration can be found `here `_. -There are two sections to the configuration. An array of ReRoutes and a GlobalConfiguration. -The ReRoutes are the objects that tell Ocelot how to treat an upstream request. The Global -configuration is a bit hacky and allows overrides of ReRoute specific settings. It's useful -if you don't want to manage lots of ReRoute specific settings. - -.. code-block:: json - - { - "ReRoutes": [], - "GlobalConfiguration": {} - } - -Here is an example ReRoute configuration, You don't need to set all of these things but this is everything that is available at the moment: - -.. code-block:: json - - { - "DownstreamPathTemplate": "/", - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": [ - "Get" - ], - "DownstreamHttpMethod": "", - "DownstreamHttpVersion": "", - "AddHeadersToRequest": {}, - "AddClaimsToRequest": {}, - "RouteClaimsRequirement": {}, - "AddQueriesToRequest": {}, - "RequestIdKey": "", - "FileCacheOptions": { - "TtlSeconds": 0, - "Region": "" - }, - "ReRouteIsCaseSensitive": false, - "ServiceName": "", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 51876, - } - ], - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 0, - "DurationOfBreak": 0, - "TimeoutValue": 0 - }, - "LoadBalancer": "", - "RateLimitOptions": { - "ClientWhitelist": [], - "EnableRateLimiting": false, - "Period": "", - "PeriodTimespan": 0, - "Limit": 0 - }, - "AuthenticationOptions": { - "AuthenticationProviderKey": "", - "AllowedScopes": [] - }, - "HttpHandlerOptions": { - "AllowAutoRedirect": true, - "UseCookieContainer": true, - "UseTracing": true, - "MaxConnectionsPerServer": 100 - }, - "DangerousAcceptAnyServerCertificateValidator": false - } - -More information on how to use these options is below.. - -Multiple environments -^^^^^^^^^^^^^^^^^^^^^ - -Like any other asp.net core project Ocelot supports configuration file names such as configuration.dev.json, configuration.test.json etc. In order to implement this add the following -to you - -.. code-block:: csharp - - .ConfigureAppConfiguration((hostingContext, config) => - { - config - .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) - .AddJsonFile("appsettings.json", true, true) - .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddJsonFile("ocelot.json") - .AddJsonFile($"configuration.{hostingContext.HostingEnvironment.EnvironmentName}.json") - .AddEnvironmentVariables(); - }) - -Ocelot will now use the environment specific configuration and fall back to ocelot.json if there isn't one. - -You also need to set the corresponding environment variable which is ASPNETCORE_ENVIRONMENT. More info on this can be found in the `asp.net core docs `_. - -Merging configuration files -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -This feature was requested in `Issue 296 `_ and allows users to have multiple configuration files to make managing large configurations easier. - -Instead of adding the configuration directly e.g. AddJsonFile("ocelot.json") you can call AddOcelot() like below. - -.. code-block:: csharp - - .ConfigureAppConfiguration((hostingContext, config) => - { - config - .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) - .AddJsonFile("appsettings.json", true, true) - .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddOcelot(hostingContext.HostingEnvironment) - .AddEnvironmentVariables(); - }) - -In this scenario Ocelot will look for any files that match the pattern (?i)ocelot.([a-zA-Z0-9]*).json and then merge these together. If you want to set the GlobalConfiguration property you must have a file called ocelot.global.json. - -The way Ocelot merges the files is basically load them, loop over them, add any ReRoutes, add any AggregateReRoutes and if the file is called ocelot.global.json add the GlobalConfiguration aswell as any ReRoutes or AggregateReRoutes. Ocelot will then save the merged configuration to a file called ocelot.json and this will be used as the source of truth while ocelot is running. - -At the moment there is no validation at this stage it only happens when Ocelot validates the final merged configuration. This is something to be aware of when you are investigating problems. I would advise always checking what is in ocelot.json if you have any problems. - -You can also give Ocelot a specific path to look in for the configuration files like below. - -.. code-block:: csharp - - .ConfigureAppConfiguration((hostingContext, config) => - { - config - .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) - .AddJsonFile("appsettings.json", true, true) - .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) - .AddOcelot("/foo/bar", hostingContext.HostingEnvironment) - .AddEnvironmentVariables(); - }) - -Ocelot needs the HostingEnvironment so it knows to exclude anything environment specific from the algorithm. - -Store configuration in 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 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() - .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. - -.. code-block:: json - - "GlobalConfiguration": { - "ServiceDiscoveryProvider": { - "Host": "localhost", - "Port": 9500 - } - } - -I decided to create this feature after working on the Raft consensus algorithm and finding out its super hard. Why not take advantage of the fact Consul already gives you this! -I guess it means if you want to use Ocelot to its fullest you take on Consul as a dependency for now. - -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 ------------------ - -If you are using Consul for configuration (or other providers in the future) you might want to key your configurations so you can have multiple configurations :) This feature was requested in `issue 346 `_! In order to specify the key you need to set the ConfigurationKey property in the ServiceDiscoveryProvider section of the configuration json file e.g. - -.. code-block:: json - - "GlobalConfiguration": { - "ServiceDiscoveryProvider": { - "Host": "localhost", - "Port": 9500, - "ConfigurationKey": "Oceolot_A" - } - } - -In this example Ocelot will use Oceolot_A as the key for your configuration when looking it up in Consul. - -If you do not set the ConfigurationKey Ocelot will use the string InternalConfiguration as the key. - -Follow Redirects / Use CookieContainer -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior: - -1. AllowAutoRedirect is a value that indicates whether the request should follow redirection responses. Set it true if the request should automatically -follow redirection responses from the Downstream resource; otherwise false. The default value is false. - -2. UseCookieContainer is a value that indicates whether the handler uses the CookieContainer -property to store server cookies and uses these cookies when sending requests. The default value is false. Please note -that if you are using the CookieContainer Ocelot caches the HttpClient for each downstream service. This means that all requests -to that DownstreamService will share the same cookies. `Issue 274 `_ was created because a user -noticed that the cookies were being shared. I tried to think of a nice way to handle this but I think it is impossible. If you don't cache the clients -that means each request gets a new client and therefore a new cookie container. If you clear the cookies from the cached client container you get race conditions due to inflight -requests. This would also mean that subsequent requests don't use the cookies from the previous response! All in all not a great situation. I would avoid setting -UseCookieContainer to true unless you have a really really good reason. Just look at your response headers and forward the cookies back with your next request! - -SSL Errors -^^^^^^^^^^ - -If you want to ignore SSL warnings / errors set the following in your ReRoute config. - -.. code-block:: json - - "DangerousAcceptAnyServerCertificateValidator": true - -I don't recommend doing this, I suggest creating your own certificate and then getting it trusted by your local / remote machine if you can. - -MaxConnectionsPerServer -^^^^^^^^^^^^^^^^^^^^^^^ - -This controls how many connections the internal HttpClient will open. This can be set at ReRoute or global level. - -React to Configuration Changes -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Resolve IOcelotConfigurationChangeTokenSource from the DI container if you wish to react to changes to the Ocelot configuration via the Ocelot.Administration API or ocelot.json being reloaded from the disk. You may either poll the change token's HasChanged property, or register a callback with the RegisterChangeCallback method. - -Polling the HasChanged property -------------------------------- - -.. code-block:: csharp - public class ConfigurationNotifyingService : BackgroundService - { - private readonly IOcelotConfigurationChangeTokenSource _tokenSource; - private readonly ILogger _logger; - public ConfigurationNotifyingService(IOcelotConfigurationChangeTokenSource tokenSource, ILogger logger) - { - _tokenSource = tokenSource; - _logger = logger; - } - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - while (!stoppingToken.IsCancellationRequested) - { - if (_tokenSource.ChangeToken.HasChanged) - { - _logger.LogInformation("Configuration updated"); - } - await Task.Delay(1000, stoppingToken); - } - } - } - -Registering a callback ----------------------- - -.. code-block:: csharp - public class MyDependencyInjectedClass : IDisposable - { - private readonly IOcelotConfigurationChangeTokenSource _tokenSource; - private readonly IDisposable _callbackHolder; - public MyClass(IOcelotConfigurationChangeTokenSource tokenSource) - { - _tokenSource = tokenSource; - _callbackHolder = tokenSource.ChangeToken.RegisterChangeCallback(_ => Console.WriteLine("Configuration changed"), null); - } - public void Dispose() - { - _callbackHolder.Dispose(); - } - } - -DownstreamHttpVersion ---------------------- - +Configuration +============ + +An example configuration can be found `here `_. There are two sections to the configuration. An array of ReRoutes and a GlobalConfiguration. The ReRoutes are the objects that tell Ocelot how to treat an upstream request. The Global configuration is a bit hacky and allows overrides of ReRoute specific settings. It's useful if you don't want to manage lots of ReRoute specific settings. + +.. code-block:: json + + { + "ReRoutes": [], + "GlobalConfiguration": {} + } + +Here is an example ReRoute configuration, You don't need to set all of these things but this is everything that is available at the moment: + +.. code-block:: json + + { + "DownstreamPathTemplate": "/", + "UpstreamPathTemplate": "/", + "UpstreamHttpMethod": [ + "Get" + ], + "DownstreamHttpMethod": "", + "DownstreamHttpVersion": "", + "AddHeadersToRequest": {}, + "AddClaimsToRequest": {}, + "RouteClaimsRequirement": {}, + "AddQueriesToRequest": {}, + "RequestIdKey": "", + "FileCacheOptions": { + "TtlSeconds": 0, + "Region": "" + }, + "ReRouteIsCaseSensitive": false, + "ServiceName": "", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 51876, + } + ], + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 0, + "DurationOfBreak": 0, + "TimeoutValue": 0 + }, + "LoadBalancer": "", + "RateLimitOptions": { + "ClientWhitelist": [], + "EnableRateLimiting": false, + "Period": "", + "PeriodTimespan": 0, + "Limit": 0 + }, + "AuthenticationOptions": { + "AuthenticationProviderKey": "", + "AllowedScopes": [] + }, + "HttpHandlerOptions": { + "AllowAutoRedirect": true, + "UseCookieContainer": true, + "UseTracing": true, + "MaxConnectionsPerServer": 100 + }, + "DangerousAcceptAnyServerCertificateValidator": false + } + +More information on how to use these options is below.. + +Multiple environments +^^^^^^^^^^^^^^^^^^^^^ + +Like any other asp.net core project Ocelot supports configuration file names such as configuration.dev.json, configuration.test.json etc. In order to implement this add the following +to you + +.. code-block:: csharp + + .ConfigureAppConfiguration((hostingContext, config) => + { + config + .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) + .AddJsonFile("appsettings.json", true, true) + .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) + .AddJsonFile("ocelot.json") + .AddJsonFile($"configuration.{hostingContext.HostingEnvironment.EnvironmentName}.json") + .AddEnvironmentVariables(); + }) + +Ocelot will now use the environment specific configuration and fall back to ocelot.json if there isn't one. + +You also need to set the corresponding environment variable which is ASPNETCORE_ENVIRONMENT. More info on this can be found in the `asp.net core docs `_. + +Merging configuration files +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +This feature was requested in `Issue 296 `_ and allows users to have multiple configuration files to make managing large configurations easier. + +Instead of adding the configuration directly e.g. AddJsonFile("ocelot.json") you can call AddOcelot() like below. + +.. code-block:: csharp + + .ConfigureAppConfiguration((hostingContext, config) => + { + config + .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) + .AddJsonFile("appsettings.json", true, true) + .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) + .AddOcelot(hostingContext.HostingEnvironment) + .AddEnvironmentVariables(); + }) + +In this scenario Ocelot will look for any files that match the pattern (?i)ocelot.([a-zA-Z0-9]*).json and then merge these together. If you want to set the GlobalConfiguration property you must have a file called ocelot.global.json. + +The way Ocelot merges the files is basically load them, loop over them, add any ReRoutes, add any AggregateReRoutes and if the file is called ocelot.global.json add the GlobalConfiguration aswell as any ReRoutes or AggregateReRoutes. Ocelot will then save the merged configuration to a file called ocelot.json and this will be used as the source of truth while ocelot is running. + +At the moment there is no validation at this stage it only happens when Ocelot validates the final merged configuration. This is something to be aware of when you are investigating problems. I would advise always checking what is in ocelot.json if you have any problems. + +You can also give Ocelot a specific path to look in for the configuration files like below. + +.. code-block:: csharp + + .ConfigureAppConfiguration((hostingContext, config) => + { + config + .SetBasePath(hostingContext.HostingEnvironment.ContentRootPath) + .AddJsonFile("appsettings.json", true, true) + .AddJsonFile($"appsettings.{hostingContext.HostingEnvironment.EnvironmentName}.json", true, true) + .AddOcelot("/foo/bar", hostingContext.HostingEnvironment) + .AddEnvironmentVariables(); + }) + +Ocelot needs the HostingEnvironment so it knows to exclude anything environment specific from the algorithm. + +Store configuration in 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 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() + .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. + +.. code-block:: json + + "GlobalConfiguration": { + "ServiceDiscoveryProvider": { + "Host": "localhost", + "Port": 9500 + } + } + +I decided to create this feature after working on the Raft consensus algorithm and finding out its super hard. Why not take advantage of the fact Consul already gives you this! I guess it means if you want to use Ocelot to its fullest you take on Consul as a dependency for now. + +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 +----------------- + +If you are using Consul for configuration (or other providers in the future) you might want to key your configurations so you can have multiple configurations :) This feature was requested in `issue 346 `_! In order to specify the key you need to set the ConfigurationKey property in the ServiceDiscoveryProvider section of the configuration json file e.g. + +.. code-block:: json + + "GlobalConfiguration": { + "ServiceDiscoveryProvider": { + "Host": "localhost", + "Port": 9500, + "ConfigurationKey": "Oceolot_A" + } + } + +In this example Ocelot will use Oceolot_A as the key for your configuration when looking it up in Consul. + +If you do not set the ConfigurationKey Ocelot will use the string InternalConfiguration as the key. + +Follow Redirects / Use CookieContainer +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Use HttpHandlerOptions in ReRoute configuration to set up HttpHandler behavior: + +1. AllowAutoRedirect is a value that indicates whether the request should follow redirection responses. Set it true if the request should automatically follow redirection responses from the Downstream resource; otherwise false. The default value is false. + +2. UseCookieContainer is a value that indicates whether the handler uses the CookieContainer property to store server cookies and uses these cookies when sending requests. The default value is false. Please note that if you are using the CookieContainer Ocelot caches the HttpClient for each downstream service. This means that all requests to that DownstreamService will share the same cookies. `Issue 274 `_ was created because a user noticed that the cookies were being shared. I tried to think of a nice way to handle this but I think it is impossible. If you don't cache the clients that means each request gets a new client and therefore a new cookie container. If you clear the cookies from the cached client container you get race conditions due to inflight requests. This would also mean that subsequent requests don't use the cookies from the previous response! All in all not a great situation. I would avoid setting UseCookieContainer to true unless you have a really really good reason. Just look at your response headers and forward the cookies back with your next request! + +SSL Errors +^^^^^^^^^^ + +If you want to ignore SSL warnings / errors set the following in your ReRoute config. + +.. code-block:: json + + "DangerousAcceptAnyServerCertificateValidator": true + +I don't recommend doing this, I suggest creating your own certificate and then getting it trusted by your local / remote machine if you can. + +MaxConnectionsPerServer +^^^^^^^^^^^^^^^^^^^^^^^ + +This controls how many connections the internal HttpClient will open. This can be set at ReRoute or global level. + +React to Configuration Changes +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Resolve IOcelotConfigurationChangeTokenSource from the DI container if you wish to react to changes to the Ocelot configuration via the Ocelot.Administration API or ocelot.json being reloaded from the disk. You may either poll the change token's HasChanged property, or register a callback with the RegisterChangeCallback method. + +Polling the HasChanged property +------------------------------- + +.. code-block:: csharp + public class ConfigurationNotifyingService : BackgroundService + { + private readonly IOcelotConfigurationChangeTokenSource _tokenSource; + private readonly ILogger _logger; + public ConfigurationNotifyingService(IOcelotConfigurationChangeTokenSource tokenSource, ILogger logger) + { + _tokenSource = tokenSource; + _logger = logger; + } + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + if (_tokenSource.ChangeToken.HasChanged) + { + _logger.LogInformation("Configuration updated"); + } + await Task.Delay(1000, stoppingToken); + } + } + } + +Registering a callback +---------------------- + +.. code-block:: csharp + public class MyDependencyInjectedClass : IDisposable + { + private readonly IOcelotConfigurationChangeTokenSource _tokenSource; + private readonly IDisposable _callbackHolder; + public MyClass(IOcelotConfigurationChangeTokenSource tokenSource) + { + _tokenSource = tokenSource; + _callbackHolder = tokenSource.ChangeToken.RegisterChangeCallback(_ => Console.WriteLine("Configuration changed"), null); + } + public void Dispose() + { + _callbackHolder.Dispose(); + } + } + +DownstreamHttpVersion +--------------------- + Ocelot allows you to choose the HTTP version it will use to make the proxy request. It can be set as "1.0", "1.1" or "2.0". \ No newline at end of file diff --git a/docs/features/delegatinghandlers.rst b/docs/features/delegatinghandlers.rst index 1da13c5f..4fc32ede 100644 --- a/docs/features/delegatinghandlers.rst +++ b/docs/features/delegatinghandlers.rst @@ -1,66 +1,64 @@ -Delegating Handlers -=================== - -Ocelot allows the user to add delegating handlers to the HttpClient transport. This feature was requested `GitHub #208 `_ -and I decided that it was going to be useful in various ways. Since then we extended it in `GitHub #264 `_. - -Usage -^^^^^ - -In order to add delegating handlers to the HttpClient transport you need to do two main things. - -First in order to create a class that can be used a delegating handler it must look as follows. We are going to register these handlers in the -asp.net core container so you can inject any other services you have registered into the constructor of your handler. - -.. code-block:: csharp - - public class FakeHandler : DelegatingHandler - { - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - //do stuff and optionally call the base handler.. - return await base.SendAsync(request, cancellationToken); - } - } - -Next you must add the handlers to Ocelot's container like below... - -.. code-block:: csharp - - services.AddOcelot() - .AddDelegatingHandler() - .AddDelegatingHandler() - -Both of these Add methods have a default parameter called global which is set to false. If it is false then the intent of -the DelegatingHandler is to be applied to specific ReRoutes via ocelot.json (more on that later). If it is set to true -then it becomes a global handler and will be applied to all ReRoutes. - -e.g. - -As below... - -.. code-block:: csharp - - services.AddOcelot() - .AddDelegatingHandler(true) - -Finally if you want ReRoute specific DelegatingHandlers or to order your specific and / or global (more on this later) DelegatingHandlers -then you must add the following json to the specific ReRoute in ocelot.json. The names in the array must match the class names of your -DelegatingHandlers for Ocelot to match them together. - -.. code-block:: json - - "DelegatingHandlers": [ - "FakeHandlerTwo", - "FakeHandler" - ] - -You can have as many DelegatingHandlers as you want and they are run in the following order: - -1. Any globals that are left in the order they were added to services and are not in the DelegatingHandlers array from ocelot.json. -2. Any non global DelegatingHandlers plus any globals that were in the DelegatingHandlers array from ocelot.json ordered as they are in the DelegatingHandlers array. -3. Tracing DelegatingHandler if enabled (see tracing docs). -4. QoS DelegatingHandler if enabled (see QoS docs). -5. The HttpClient sends the HttpRequestMessage. - -Hopefully other people will find this feature useful! +Delegating Handlers +=================== + +Ocelot allows the user to add delegating handlers to the HttpClient transport. This feature was requested `GitHub #208 `_ +and I decided that it was going to be useful in various ways. Since then we extended it in `GitHub #264 `_. + +Usage +^^^^^ + +In order to add delegating handlers to the HttpClient transport you need to do two main things. + +First in order to create a class that can be used a delegating handler it must look as follows. We are going to register these handlers in the +asp.net core container so you can inject any other services you have registered into the constructor of your handler. + +.. code-block:: csharp + + public class FakeHandler : DelegatingHandler + { + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + //do stuff and optionally call the base handler.. + return await base.SendAsync(request, cancellationToken); + } + } + +Next you must add the handlers to Ocelot's container like below... + +.. code-block:: csharp + + services.AddOcelot() + .AddDelegatingHandler() + .AddDelegatingHandler() + +Both of these Add methods have a default parameter called global which is set to false. If it is false then the intent of the DelegatingHandler is to be applied to specific ReRoutes via ocelot.json (more on that later). If it is set to true +then it becomes a global handler and will be applied to all ReRoutes. + +e.g. + +As below... + +.. code-block:: csharp + + services.AddOcelot() + .AddDelegatingHandler(true) + +Finally if you want ReRoute specific DelegatingHandlers or to order your specific and / or global (more on this later) DelegatingHandlers then you must add the following json to the specific ReRoute in ocelot.json. The names in the array must match the class names of your +DelegatingHandlers for Ocelot to match them together. + +.. code-block:: json + + "DelegatingHandlers": [ + "FakeHandlerTwo", + "FakeHandler" + ] + +You can have as many DelegatingHandlers as you want and they are run in the following order: + +1. Any globals that are left in the order they were added to services and are not in the DelegatingHandlers array from ocelot.json. +2. Any non global DelegatingHandlers plus any globals that were in the DelegatingHandlers array from ocelot.json ordered as they are in the DelegatingHandlers array. +3. Tracing DelegatingHandler if enabled (see tracing docs). +4. QoS DelegatingHandler if enabled (see QoS docs). +5. The HttpClient sends the HttpRequestMessage. + +Hopefully other people will find this feature useful! diff --git a/docs/features/errorcodes.rst b/docs/features/errorcodes.rst index fe058790..aeea7b63 100644 --- a/docs/features/errorcodes.rst +++ b/docs/features/errorcodes.rst @@ -1,13 +1,13 @@ -Http Error Status Codes -======================= - -Ocelot will return HTTP status error codes based on internal logic in certain siturations: -- 401 if the authentication middleware runs and the user is not authenticated. -- 403 if the authorisation middleware runs and the user is unauthenticated, claim value not authroised, scope not authorised, user doesnt have required claim or cannot find claim. -- 503 if the downstream request times out. -- 499 if the request is cancelled by the client. -- 404 if unable to find a downstream route. -- 502 if unable to connect to downstream service. -- 500 if unable to complete the HTTP request downstream and the exception is not OperationCanceledException or HttpRequestException. -- 404 if Ocelot is unable to map an internal error code to a HTTP status code. - +Http Error Status Codes +======================= + +Ocelot will return HTTP status error codes based on internal logic in certain siturations: +- 401 if the authentication middleware runs and the user is not authenticated. +- 403 if the authorisation middleware runs and the user is unauthenticated, claim value not authroised, scope not authorised, user doesnt have required claim or cannot find claim. +- 503 if the downstream request times out. +- 499 if the request is cancelled by the client. +- 404 if unable to find a downstream route. +- 502 if unable to connect to downstream service. +- 500 if unable to complete the HTTP request downstream and the exception is not OperationCanceledException or HttpRequestException. +- 404 if Ocelot is unable to map an internal error code to a HTTP status code. + diff --git a/docs/features/graphql.rst b/docs/features/graphql.rst index 36006fae..df0b2b00 100644 --- a/docs/features/graphql.rst +++ b/docs/features/graphql.rst @@ -1,14 +1,11 @@ GraphQL ======= -OK you got me Ocelot doesn't directly support GraphQL but so many people have asked about it I wanted to show how easy it is to integrate -the `graphql-dotnet `_ library. +OK you got me Ocelot doesn't directly support GraphQL but so many people have asked about it I wanted to show how easy it is to integrate the `graphql-dotnet `_ library. -Please see the sample project `OcelotGraphQL `_. -Using a combination of the graphql-dotnet project and Ocelot's DelegatingHandler features this is pretty easy to do. -However I do not intend to integrate more closely with GraphQL at the moment. Check out the samples readme and that should give -you enough instruction on how to do this! +Please see the sample project `OcelotGraphQL `_. Using a combination of the graphql-dotnet project and Ocelot's DelegatingHandler features this is pretty easy to do. +However I do not intend to integrate more closely with GraphQL at the moment. Check out the samples readme and that should give you enough instruction on how to do this! Good luck and have fun :> diff --git a/docs/features/headerstransformation.rst b/docs/features/headerstransformation.rst index 41c93f5d..0809d8bf 100644 --- a/docs/features/headerstransformation.rst +++ b/docs/features/headerstransformation.rst @@ -1,151 +1,150 @@ -Headers Transformation -====================== - -Ocelot allows the user to transform headers pre and post downstream request. At the moment Ocelot only supports find and replace. This feature was requested `GitHub #190 `_ and I decided that it was going to be useful in various ways. - -Add to Request -^^^^^^^^^^^^^^ - -This feature was requestes in `GitHub #313 `_. - -If you want to add a header to your upstream request please add the following to a ReRoute in your ocelot.json: - -.. code-block:: json - - "UpstreamHeaderTransform": { - "Uncle": "Bob" - } - -In the example above a header with the key Uncle and value Bob would be send to to the upstream service. - -Placeholders are supported too (see below). - -Add to Response -^^^^^^^^^^^^^^^ - -This feature was requested in `GitHub #280 `_. - -If you want to add a header to your downstream response please add the following to a ReRoute in ocelot.json.. - -.. code-block:: json - - "DownstreamHeaderTransform": { - "Uncle": "Bob" - }, - -In the example above a header with the key Uncle and value Bob would be returned by Ocelot when requesting the specific ReRoute. - -If you want to return the Butterfly APM trace id then do something like the following.. - -.. code-block:: json - - "DownstreamHeaderTransform": { - "AnyKey": "{TraceId}" - }, - -Find and Replace -^^^^^^^^^^^^^^^^ - -In order to transform a header first we specify the header key and then the type of transform we want e.g. - -.. code-block:: json - - "Test": "http://www.bbc.co.uk/, http://ocelot.com/" - -The key is "Test" and the value is "http://www.bbc.co.uk/, http://ocelot.com/". The value is saying replace http://www.bbc.co.uk/ with http://ocelot.com/. The syntax is {find}, {replace}. Hopefully pretty simple. There are examples below that explain more. - -Pre Downstream Request -^^^^^^^^^^^^^^^^^^^^^^ - -Add the following to a ReRoute in ocelot.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This header will be changed before the request downstream and will be sent to the downstream server. - -.. code-block:: json - - "UpstreamHeaderTransform": { - "Test": "http://www.bbc.co.uk/, http://ocelot.com/" - }, - -Post Downstream Request -^^^^^^^^^^^^^^^^^^^^^^^ - -Add the following to a ReRoute in ocelot.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This transformation will take place after Ocelot has received the response from the downstream service. - -.. code-block:: json - - "DownstreamHeaderTransform": { - "Test": "http://www.bbc.co.uk/, http://ocelot.com/" - }, - -Placeholders -^^^^^^^^^^^^ - -Ocelot allows placeholders that can be used in header transformation. - -{RemoteIpAddress} - This will find the clients IP address using _httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString() so you will get back some IP. -{BaseUrl} - This will use Ocelot's base url e.g. http://localhost:5000 as its value. -{DownstreamBaseUrl} - This will use the downstream services base url e.g. http://localhost:5000 as its value. This only works for DownstreamHeaderTransform at the moment. -{TraceId} - This will use the Butterfly APM Trace Id. This only works for DownstreamHeaderTransform at the moment. -{UpstreamHost} - This will look for the incoming Host header. - -Handling 302 Redirects -^^^^^^^^^^^^^^^^^^^^^^ -Ocelot will by default automatically follow redirects however if you want to return the location header to the client you might want to change the location to be Ocelot not the downstream service. Ocelot allows this with the following configuration. - -.. code-block:: json - - "DownstreamHeaderTransform": { - "Location": "http://www.bbc.co.uk/, http://ocelot.com/" - }, - "HttpHandlerOptions": { - "AllowAutoRedirect": false, - }, - -or you could use the BaseUrl placeholder. - -.. code-block:: json - - "DownstreamHeaderTransform": { - "Location": "http://localhost:6773, {BaseUrl}" - }, - "HttpHandlerOptions": { - "AllowAutoRedirect": false, - }, - -finally if you are using a load balancer with Ocelot you will get multiple downstream base urls so the above would not work. In this case you can do the following. - -.. code-block:: json - - "DownstreamHeaderTransform": { - "Location": "{DownstreamBaseUrl}, {BaseUrl}" - }, - "HttpHandlerOptions": { - "AllowAutoRedirect": false, - }, - -X-Forwarded-For -^^^^^^^^^^^^^^^ - -An example of using {RemoteIpAddress} placeholder... - -.. code-block:: json - - "UpstreamHeaderTransform": { - "X-Forwarded-For": "{RemoteIpAddress}" - } - -Future -^^^^^^ - -Ideally this feature would be able to support the fact that a header can have multiple values. At the moment it just assumes one. -It would also be nice if it could multi find and replace e.g. - -.. code-block:: json - - "DownstreamHeaderTransform": { - "Location": "[{one,one},{two,two}" - }, - "HttpHandlerOptions": { - "AllowAutoRedirect": false, - }, - -If anyone wants to have a go at this please help yourself!! +Headers Transformation +====================== + +Ocelot allows the user to transform headers pre and post downstream request. At the moment Ocelot only supports find and replace. This feature was requested `GitHub #190 `_ and I decided that it was going to be useful in various ways. + +Add to Request +^^^^^^^^^^^^^^ + +This feature was requestes in `GitHub #313 `_. + +If you want to add a header to your upstream request please add the following to a ReRoute in your ocelot.json: + +.. code-block:: json + + "UpstreamHeaderTransform": { + "Uncle": "Bob" + } + +In the example above a header with the key Uncle and value Bob would be send to to the upstream service. + +Placeholders are supported too (see below). + +Add to Response +^^^^^^^^^^^^^^^ + +This feature was requested in `GitHub #280 `_. + +If you want to add a header to your downstream response please add the following to a ReRoute in ocelot.json.. + +.. code-block:: json + + "DownstreamHeaderTransform": { + "Uncle": "Bob" + }, + +In the example above a header with the key Uncle and value Bob would be returned by Ocelot when requesting the specific ReRoute. + +If you want to return the Butterfly APM trace id then do something like the following.. + +.. code-block:: json + + "DownstreamHeaderTransform": { + "AnyKey": "{TraceId}" + }, + +Find and Replace +^^^^^^^^^^^^^^^^ + +In order to transform a header first we specify the header key and then the type of transform we want e.g. + +.. code-block:: json + + "Test": "http://www.bbc.co.uk/, http://ocelot.com/" + +The key is "Test" and the value is "http://www.bbc.co.uk/, http://ocelot.com/". The value is saying replace http://www.bbc.co.uk/ with http://ocelot.com/. The syntax is {find}, {replace}. Hopefully pretty simple. There are examples below that explain more. + +Pre Downstream Request +^^^^^^^^^^^^^^^^^^^^^^ + +Add the following to a ReRoute in ocelot.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This header will be changed before the request downstream and will be sent to the downstream server. + +.. code-block:: json + + "UpstreamHeaderTransform": { + "Test": "http://www.bbc.co.uk/, http://ocelot.com/" + }, + +Post Downstream Request +^^^^^^^^^^^^^^^^^^^^^^^ + +Add the following to a ReRoute in ocelot.json in order to replace http://www.bbc.co.uk/ with http://ocelot.com/. This transformation will take place after Ocelot has received the response from the downstream service. + +.. code-block:: json + + "DownstreamHeaderTransform": { + "Test": "http://www.bbc.co.uk/, http://ocelot.com/" + }, + +Placeholders +^^^^^^^^^^^^ + +Ocelot allows placeholders that can be used in header transformation. + +{RemoteIpAddress} - This will find the clients IP address using _httpContextAccessor.HttpContext.Connection.RemoteIpAddress.ToString() so you will get back some IP. +{BaseUrl} - This will use Ocelot's base url e.g. http://localhost:5000 as its value. +{DownstreamBaseUrl} - This will use the downstream services base url e.g. http://localhost:5000 as its value. This only works for DownstreamHeaderTransform at the moment. +{TraceId} - This will use the Butterfly APM Trace Id. This only works for DownstreamHeaderTransform at the moment. +{UpstreamHost} - This will look for the incoming Host header. + +Handling 302 Redirects +^^^^^^^^^^^^^^^^^^^^^^ +Ocelot will by default automatically follow redirects however if you want to return the location header to the client you might want to change the location to be Ocelot not the downstream service. Ocelot allows this with the following configuration. + +.. code-block:: json + + "DownstreamHeaderTransform": { + "Location": "http://www.bbc.co.uk/, http://ocelot.com/" + }, + "HttpHandlerOptions": { + "AllowAutoRedirect": false, + }, + +or you could use the BaseUrl placeholder. + +.. code-block:: json + + "DownstreamHeaderTransform": { + "Location": "http://localhost:6773, {BaseUrl}" + }, + "HttpHandlerOptions": { + "AllowAutoRedirect": false, + }, + +finally if you are using a load balancer with Ocelot you will get multiple downstream base urls so the above would not work. In this case you can do the following. + +.. code-block:: json + + "DownstreamHeaderTransform": { + "Location": "{DownstreamBaseUrl}, {BaseUrl}" + }, + "HttpHandlerOptions": { + "AllowAutoRedirect": false, + }, + +X-Forwarded-For +^^^^^^^^^^^^^^^ + +An example of using {RemoteIpAddress} placeholder... + +.. code-block:: json + + "UpstreamHeaderTransform": { + "X-Forwarded-For": "{RemoteIpAddress}" + } + +Future +^^^^^^ + +Ideally this feature would be able to support the fact that a header can have multiple values. At the moment it just assumes one. It would also be nice if it could multi find and replace e.g. + +.. code-block:: json + + "DownstreamHeaderTransform": { + "Location": "[{one,one},{two,two}" + }, + "HttpHandlerOptions": { + "AllowAutoRedirect": false, + }, + +If anyone wants to have a go at this please help yourself!! diff --git a/docs/features/kubernetes.rst b/docs/features/kubernetes.rst index 90120e5b..a756577e 100644 --- a/docs/features/kubernetes.rst +++ b/docs/features/kubernetes.rst @@ -1,97 +1,95 @@ -Kubernetes -============== - -This feature was requested as part of `Issue 345 `_ . to add support for kubernetes's provider. - -Ocelot will call the k8s endpoints API in a given namespace to get all of the endpoints for a pod and then load balance across them. Ocelot used to use the services api to send requests to the k8s service but this was changed in `PR 1134 `_ because the service did not load balance as expected. - -The first thing you need to do is install the NuGet package that provides kubernetes support in Ocelot. - -``Install-Package Ocelot.Provider.Kubernetes`` - -Then add the following to your ConfigureServices method. - -.. code-block:: csharp - - s.AddOcelot() - .AddKubernetes(); - -If you have services deployed in kubernetes you will normally use the naming service to access them. Default usePodServiceAccount = True, which means that ServiceAccount using Pod to access the service of the k8s cluster needs to be ServiceAccount based on RBAC authorization - -.. code-block::csharp - public static class OcelotBuilderExtensions - { - public static IOcelotBuilder AddKubernetes(this IOcelotBuilder builder, bool usePodServiceAccount = true); - } - -You can replicate a Permissive. Using RBAC role bindings. -`Permissive RBAC Permissions `_, k8s api server and token will read from pod. - -.. code-block::bash -kubectl create clusterrolebinding permissive-binding --clusterrole=cluster-admin --user=admin --user=kubelet --group=system:serviceaccounts - -The following example shows how to set up a ReRoute that will work in kubernetes. The most important thing is the ServiceName which is made up of the -kubernetes service name. We also need to set up the ServiceDiscoveryProvider in GlobalConfiguration. The example here shows a typical configuration. - - -.. code-block:: json - - { - "ReRoutes": [ - { - "DownstreamPathTemplate": "/api/values", - "DownstreamScheme": "http", - "UpstreamPathTemplate": "/values", - "ServiceName": "downstreamservice", - "UpstreamHttpMethod": [ "Get" ] - } - ], - "GlobalConfiguration": { - "ServiceDiscoveryProvider": { - "Host": "192.168.0.13", - "Port": 443, - "Token": "txpc696iUhbVoudg164r93CxDTrKRVWG", - "Namespace": "dev", - "Type": "kube" - } - } -} - -Service deployment in Namespace Dev , ServiceDiscoveryProvider type is kube, you also can set pollkube ServiceDiscoveryProvider type. - Note: Host、 Port and Token are no longer in use。 - -You use Ocelot to poll kubernetes for latest service information rather than per request. If you want to poll kubernetes for the latest services rather than per request (default behaviour) then you need to set the following configuration. - -.. code-block:: json - - "ServiceDiscoveryProvider": { - "Host": "192.168.0.13", - "Port": 443, - "Token": "txpc696iUhbVoudg164r93CxDTrKRVWG", - "Namespace": "dev", - "Type": "pollkube", - "PollingInterval": 100 - } - -The polling interval is in milliseconds and tells Ocelot how often to call kubernetes for changes in service configuration. - -Please note there are tradeoffs here. If you poll kubernetes 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 volatile your services are. I doubt it will matter for most people and polling may give a tiny performance improvement over calling kubernetes per request. -There is no way for Ocelot to work these out for you. - -If your downstream service resides in a different namespace you can override the global setting at the ReRoute level by specifying a ServiceNamespace. - - -.. code-block:: json - - { - "ReRoutes": [ - { - "DownstreamPathTemplate": "/api/values", - "DownstreamScheme": "http", - "UpstreamPathTemplate": "/values", - "ServiceName": "downstreamservice", - "ServiceNamespace": "downstream-namespace", - "UpstreamHttpMethod": [ "Get" ] - } - ] - } +Kubernetes +============== + +This feature was requested as part of `Issue 345 `_ . to add support for kubernetes's provider. + +Ocelot will call the k8s endpoints API in a given namespace to get all of the endpoints for a pod and then load balance across them. Ocelot used to use the services api to send requests to the k8s service but this was changed in `PR 1134 `_ because the service did not load balance as expected. + +The first thing you need to do is install the NuGet package that provides kubernetes support in Ocelot. + +``Install-Package Ocelot.Provider.Kubernetes`` + +Then add the following to your ConfigureServices method. + +.. code-block:: csharp + + s.AddOcelot() + .AddKubernetes(); + +If you have services deployed in kubernetes you will normally use the naming service to access them. Default usePodServiceAccount = True, which means that ServiceAccount using Pod to access the service of the k8s cluster needs to be ServiceAccount based on RBAC authorization + +.. code-block::csharp + public static class OcelotBuilderExtensions + { + public static IOcelotBuilder AddKubernetes(this IOcelotBuilder builder, bool usePodServiceAccount = true); + } + +You can replicate a Permissive. Using RBAC role bindings. +`Permissive RBAC Permissions `_, k8s api server and token will read from pod. + +.. code-block::bash +kubectl create clusterrolebinding permissive-binding --clusterrole=cluster-admin --user=admin --user=kubelet --group=system:serviceaccounts + +The following example shows how to set up a ReRoute that will work in kubernetes. The most important thing is the ServiceName which is made up of the kubernetes service name. We also need to set up the ServiceDiscoveryProvider in GlobalConfiguration. The example here shows a typical configuration. + + +.. code-block:: json + + { + "ReRoutes": [ + { + "DownstreamPathTemplate": "/api/values", + "DownstreamScheme": "http", + "UpstreamPathTemplate": "/values", + "ServiceName": "downstreamservice", + "UpstreamHttpMethod": [ "Get" ] + } + ], + "GlobalConfiguration": { + "ServiceDiscoveryProvider": { + "Host": "192.168.0.13", + "Port": 443, + "Token": "txpc696iUhbVoudg164r93CxDTrKRVWG", + "Namespace": "dev", + "Type": "kube" + } + } +} + +Service deployment in Namespace Dev , ServiceDiscoveryProvider type is kube, you also can set pollkube ServiceDiscoveryProvider type. + Note: Host、 Port and Token are no longer in use。 + +You use Ocelot to poll kubernetes for latest service information rather than per request. If you want to poll kubernetes for the latest services rather than per request (default behaviour) then you need to set the following configuration. + +.. code-block:: json + + "ServiceDiscoveryProvider": { + "Host": "192.168.0.13", + "Port": 443, + "Token": "txpc696iUhbVoudg164r93CxDTrKRVWG", + "Namespace": "dev", + "Type": "pollkube", + "PollingInterval": 100 + } + +The polling interval is in milliseconds and tells Ocelot how often to call kubernetes for changes in service configuration. + +Please note there are tradeoffs here. If you poll kubernetes 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 volatile your services are. I doubt it will matter for most people and polling may give a tiny performance improvement over calling kubernetes per request. There is no way for Ocelot to work these out for you. + +If your downstream service resides in a different namespace you can override the global setting at the ReRoute level by specifying a ServiceNamespace. + + +.. code-block:: json + + { + "ReRoutes": [ + { + "DownstreamPathTemplate": "/api/values", + "DownstreamScheme": "http", + "UpstreamPathTemplate": "/values", + "ServiceName": "downstreamservice", + "ServiceNamespace": "downstream-namespace", + "UpstreamHttpMethod": [ "Get" ] + } + ] + } diff --git a/docs/features/loadbalancer.rst b/docs/features/loadbalancer.rst index 02905094..0bd27305 100644 --- a/docs/features/loadbalancer.rst +++ b/docs/features/loadbalancer.rst @@ -1,219 +1,210 @@ -Load Balancer -============= - -Ocelot can load balance across available downstream services for each ReRoute. This means you can scale your downstream services and Ocelot can use them effectively. - -The type of load balancer available are: - - LeastConnection - tracks which services are dealing with requests and sends new requests to service with least existing requests. The algorythm state is not distributed across a cluster of Ocelot's. - - RoundRobin - loops through available services and sends requests. The algorythm state is not distributed across a cluster of Ocelot's. - - NoLoadBalancer - takes the first available service from config or service discovery. - - CookieStickySessions - uses a cookie to stick all requests to a specific server. More info below. - -You must choose in your configuration which load balancer to use. - -Configuration -^^^^^^^^^^^^^ - -The following shows how to set up multiple downstream services for a ReRoute using ocelot.json and then select the LeastConnection load balancer. This is the simplest way to get load balancing set up. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "10.0.1.10", - "Port": 5000, - }, - { - "Host": "10.0.1.11", - "Port": 5000, - } - ], - "UpstreamPathTemplate": "/posts/{postId}", - "LoadBalancerOptions": { - "Type": "LeastConnection" - }, - "UpstreamHttpMethod": [ "Put", "Delete" ] - } - - -Service Discovery -^^^^^^^^^^^^^^^^^ - -The following shows how to set up a ReRoute using service discovery then select the LeastConnection load balancer. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": [ "Put" ], - "ServiceName": "product", - "LoadBalancerOptions": { - "Type": "LeastConnection" - }, - } - -When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. If you add and remove services from the -service discovery provider (consul) then Ocelot should respect this and stop calling services that have been removed and start calling services that have been added. - -CookieStickySessions -^^^^^^^^^^^^^^^^^^^^ - -I've implemented a really basic sticky session type of load balancer. The scenario it is meant to support is you have a bunch of downstream -servers that don't share session state so if you get more than one request for one of these servers then it should go to the same box each -time or the session state might be incorrect for the given user. This feature was requested in `Issue #322 `_ -though what the user wants is more complicated than just sticky sessions :) anyway I thought this would be a nice feature to have! - -In order to set up CookieStickySessions load balancer you need to do something like the following. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "10.0.1.10", - "Port": 5000, - }, - { - "Host": "10.0.1.11", - "Port": 5000, - } - ], - "UpstreamPathTemplate": "/posts/{postId}", - "LoadBalancerOptions": { - "Type": "CookieStickySessions", - "Key": "ASP.NET_SessionId", - "Expiry": 1800000 - }, - "UpstreamHttpMethod": [ "Put", "Delete" ] - } - -The LoadBalancerOptions are Type this needs to be CookieStickySessions, Key this is the key of the cookie you -wish to use for the sticky sessions, Expiry this is how long in milliseconds you want to the session to be stuck for. Remember this -refreshes on every request which is meant to mimick how sessions work usually. - -If you have multiple ReRoutes with the same LoadBalancerOptions then all of those ReRoutes will use the same load balancer for there -subsequent requests. This means the sessions will be stuck across ReRoutes. - -Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul -and this returns more than one service then CookieStickySessions uses round robin to select the next server. This is hard coded at the -moment but could be changed. - -Custom Load Balancers -^^^^^^^^^^^^^^^^^^^^ - -`DavidLievrouw >> _services; - private readonly object _lock = new object(); - - private int _last; - - public CustomLoadBalancer(Func>> services) - { - _services = services; - } - - public async Task> Lease(DownstreamContext downstreamContext) - { - var services = await _services(); - lock (_lock) - { - if (_last >= services.Count) - { - _last = 0; - } - - var next = services[_last]; - _last++; - return new OkResponse(next.HostAndPort); - } - } - - public void Release(ServiceHostAndPort hostAndPort) - { - } - } - -Finally you need to register this class with Ocelot. I have used the most complex example below to show all of the data / types that can be passed into the factory that creates load balancers. - -.. code-block:: csharp - - Func loadBalancerFactoryFunc = (serviceProvider, reRoute, serviceDiscoveryProvider) => new CustomLoadBalancer(serviceDiscoveryProvider.Get); - - s.AddOcelot() - .AddCustomLoadBalancer(loadBalancerFactoryFunc); - -However there is a much simpler example that will work the same. - -.. code-block:: csharp - - s.AddOcelot() - .AddCustomLoadBalancer(); - -There are numerous extension methods to add a custom load balancer and the interface is as follows. - -.. code-block:: csharp - - IOcelotBuilder AddCustomLoadBalancer() - where T : ILoadBalancer, new(); - - IOcelotBuilder AddCustomLoadBalancer(Func loadBalancerFactoryFunc) - where T : ILoadBalancer; - - IOcelotBuilder AddCustomLoadBalancer(Func loadBalancerFactoryFunc) - where T : ILoadBalancer; - - IOcelotBuilder AddCustomLoadBalancer( - Func loadBalancerFactoryFunc) - where T : ILoadBalancer; - - IOcelotBuilder AddCustomLoadBalancer( - Func loadBalancerFactoryFunc) - where T : ILoadBalancer; - -When you enable custom load balancers Ocelot looks up your load balancer by its class name when it decides if it should do load balancing. If it finds a match it will use your load balaner to load balance. If Ocelot cannot match the load balancer type in your configuration with the name of registered load balancer class then you will receive a HTTP 500 internal server error. If your load balancer factory throw an exception when Ocelot calls it you will receive a HTTP 500 internal server error. - +Load Balancer +============= + +Ocelot can load balance across available downstream services for each ReRoute. This means you can scale your downstream services and Ocelot can use them effectively. + +The type of load balancer available are: + + LeastConnection - tracks which services are dealing with requests and sends new requests to service with least existing requests. The algorythm state is not distributed across a cluster of Ocelot's. + + RoundRobin - loops through available services and sends requests. The algorythm state is not distributed across a cluster of Ocelot's. + + NoLoadBalancer - takes the first available service from config or service discovery. + + CookieStickySessions - uses a cookie to stick all requests to a specific server. More info below. + +You must choose in your configuration which load balancer to use. + +Configuration +^^^^^^^^^^^^^ + +The following shows how to set up multiple downstream services for a ReRoute using ocelot.json and then select the LeastConnection load balancer. This is the simplest way to get load balancing set up. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "10.0.1.10", + "Port": 5000, + }, + { + "Host": "10.0.1.11", + "Port": 5000, + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "LoadBalancerOptions": { + "Type": "LeastConnection" + }, + "UpstreamHttpMethod": [ "Put", "Delete" ] + } + + +Service Discovery +^^^^^^^^^^^^^^^^^ + +The following shows how to set up a ReRoute using service discovery then select the LeastConnection load balancer. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Put" ], + "ServiceName": "product", + "LoadBalancerOptions": { + "Type": "LeastConnection" + }, + } + +When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. If you add and remove services from the +service discovery provider (consul) then Ocelot should respect this and stop calling services that have been removed and start calling services that have been added. + +CookieStickySessions +^^^^^^^^^^^^^^^^^^^^ + +I've implemented a really basic sticky session type of load balancer. The scenario it is meant to support is you have a bunch of downstream servers that don't share session state so if you get more than one request for one of these servers then it should go to the same box each time or the session state might be incorrect for the given user. This feature was requested in `Issue #322 `_ though what the user wants is more complicated than just sticky sessions :) anyway I thought this would be a nice feature to have! + +In order to set up CookieStickySessions load balancer you need to do something like the following. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "10.0.1.10", + "Port": 5000, + }, + { + "Host": "10.0.1.11", + "Port": 5000, + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "LoadBalancerOptions": { + "Type": "CookieStickySessions", + "Key": "ASP.NET_SessionId", + "Expiry": 1800000 + }, + "UpstreamHttpMethod": [ "Put", "Delete" ] + } + +The LoadBalancerOptions are Type this needs to be CookieStickySessions, Key this is the key of the cookie you wish to use for the sticky sessions, Expiry this is how long in milliseconds you want to the session to be stuck for. Remember this refreshes on every request which is meant to mimick how sessions work usually. + +If you have multiple ReRoutes with the same LoadBalancerOptions then all of those ReRoutes will use the same load balancer for there subsequent requests. This means the sessions will be stuck across ReRoutes. + +Please note that if you give more than one DownstreamHostAndPort or you are using a Service Discovery provider such as Consul and this returns more than one service then CookieStickySessions uses round robin to select the next server. This is hard coded at the moment but could be changed. + +Custom Load Balancers +^^^^^^^^^^^^^^^^^^^^ + +`DavidLievrouw >> _services; + private readonly object _lock = new object(); + + private int _last; + + public CustomLoadBalancer(Func>> services) + { + _services = services; + } + + public async Task> Lease(DownstreamContext downstreamContext, HttpContext httpContext) + { + var services = await _services(); + lock (_lock) + { + if (_last >= services.Count) + { + _last = 0; + } + + var next = services[_last]; + _last++; + return new OkResponse(next.HostAndPort); + } + } + + public void Release(ServiceHostAndPort hostAndPort) + { + } + } + +Finally you need to register this class with Ocelot. I have used the most complex example below to show all of the data / types that can be passed into the factory that creates load balancers. + +.. code-block:: csharp + + Func loadBalancerFactoryFunc = (serviceProvider, reRoute, serviceDiscoveryProvider) => new CustomLoadBalancer(serviceDiscoveryProvider.Get); + + s.AddOcelot() + .AddCustomLoadBalancer(loadBalancerFactoryFunc); + +However there is a much simpler example that will work the same. + +.. code-block:: csharp + + s.AddOcelot() + .AddCustomLoadBalancer(); + +There are numerous extension methods to add a custom load balancer and the interface is as follows. + +.. code-block:: csharp + + IOcelotBuilder AddCustomLoadBalancer() + where T : ILoadBalancer, new(); + + IOcelotBuilder AddCustomLoadBalancer(Func loadBalancerFactoryFunc) + where T : ILoadBalancer; + + IOcelotBuilder AddCustomLoadBalancer(Func loadBalancerFactoryFunc) + where T : ILoadBalancer; + + IOcelotBuilder AddCustomLoadBalancer( + Func loadBalancerFactoryFunc) + where T : ILoadBalancer; + + IOcelotBuilder AddCustomLoadBalancer( + Func loadBalancerFactoryFunc) + where T : ILoadBalancer; + +When you enable custom load balancers Ocelot looks up your load balancer by its class name when it decides if it should do load balancing. If it finds a match it will use your load balaner to load balance. If Ocelot cannot match the load balancer type in your configuration with the name of registered load balancer class then you will receive a HTTP 500 internal server error. If your load balancer factory throw an exception when Ocelot calls it you will receive a HTTP 500 internal server error. + Remember if you specify no load balancer in your config Ocelot will not try and load balance. \ No newline at end of file diff --git a/docs/features/logging.rst b/docs/features/logging.rst index b09a26cf..c79c7743 100644 --- a/docs/features/logging.rst +++ b/docs/features/logging.rst @@ -1,20 +1,17 @@ Logging ======= -Ocelot uses the standard logging interfaces ILoggerFactory / ILogger at the moment. -This is encapsulated in IOcelotLogger / IOcelotLoggerFactory with an implementation +Ocelot uses the standard logging interfaces ILoggerFactory / ILogger at the moment. This is encapsulated in IOcelotLogger / IOcelotLoggerFactory with an implementation for the standard asp.net core logging stuff at the moment. This is because Ocelot add's some extra info to the logs such as request id if it is configured. There is a global error handler that should catch any exceptions thrown and log them as errors. Finally if logging is set to trace level Ocelot will log starting, finishing and any middlewares that throw an exception which can be quite useful. -The reason for not just using bog standard framework logging is that I could not -work out how to override the request id that get's logged when setting IncludeScopes +The reason for not just using bog standard framework logging is that I could not work out how to override the request id that get's logged when setting IncludeScopes to true for logging settings. Nicely onto the next feature. Warning ^^^^^^^ -If you are logging to Console you will get terrible performance. I have had so many issues about performance issues with Ocelot -and it is always logging level Debug, logging to Console :) Make sure you are logging to something proper in production :) +If you are logging to Console you will get terrible performance. I have had so many issues about performance issues with Ocelot and it is always logging level Debug, logging to Console :) Make sure you are logging to something proper in production :) diff --git a/docs/features/methodtransformation.rst b/docs/features/methodtransformation.rst index 5b1c1518..feecb7fa 100644 --- a/docs/features/methodtransformation.rst +++ b/docs/features/methodtransformation.rst @@ -1,28 +1,28 @@ -HTTP Method Transformation -========================== - -Ocelot allows the user to change the HTTP request method that will be used when making a request to a downstream service. - -This achieved by setting the following ReRoute configuration: - -.. code-block:: json - - { - "DownstreamPathTemplate": "/{url}", - "UpstreamPathTemplate": "/{url}", - "UpstreamHttpMethod": [ - "Get" - ], - "DownstreamHttpMethod": "POST", - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 53271 - } - ], - } - -The key property here is DownstreamHttpMethod which is set as POST and the ReRoute will only match on GET as set by UpstreamHttpMethod. - +HTTP Method Transformation +========================== + +Ocelot allows the user to change the HTTP request method that will be used when making a request to a downstream service. + +This achieved by setting the following ReRoute configuration: + +.. code-block:: json + + { + "DownstreamPathTemplate": "/{url}", + "UpstreamPathTemplate": "/{url}", + "UpstreamHttpMethod": [ + "Get" + ], + "DownstreamHttpMethod": "POST", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 53271 + } + ], + } + +The key property here is DownstreamHttpMethod which is set as POST and the ReRoute will only match on GET as set by UpstreamHttpMethod. + This feature can be useful when interacting with downstream apis that only support POST and you want to present some kind of RESTful interface. \ No newline at end of file diff --git a/docs/features/qualityofservice.rst b/docs/features/qualityofservice.rst index 9839b2f7..b7dfa7f2 100644 --- a/docs/features/qualityofservice.rst +++ b/docs/features/qualityofservice.rst @@ -1,8 +1,7 @@ Quality of Service ================== -Ocelot supports one QoS capability at the current time. You can set on a per ReRoute basis if you -want to use a circuit breaker when making requests to a downstream service. This uses an awesome +Ocelot supports one QoS capability at the current time. You can set on a per ReRoute basis if you want to use a circuit breaker when making requests to a downstream service. This uses an awesome .NET library called Polly check them out `here `_. The first thing you need to do if you want to use the administration API is bring in the relevant NuGet package.. @@ -30,8 +29,7 @@ Then add the following section to a ReRoute configuration. "TimeoutValue":5000 } -You must set a number greater than 0 against ExceptionsAllowedBeforeBreaking for this rule to be -implemented. Duration of break means the circuit breaker will stay open for 1 second after it is tripped. +You must set a number greater than 0 against ExceptionsAllowedBeforeBreaking for this rule to be implemented. Duration of break means the circuit breaker will stay open for 1 second after it is tripped. TimeoutValue means if a request takes more than 5 seconds it will automatically be timed out. You can set the TimeoutValue in isolation of the ExceptionsAllowedBeforeBreaking and DurationOfBreak options. @@ -44,5 +42,4 @@ You can set the TimeoutValue in isolation of the ExceptionsAllowedBeforeBreaking There is no point setting the other two in isolation as they affect each other :) -If you do not add a QoS section QoS will not be used however Ocelot will default to a 90 second timeout -on all downstream requests. If someone needs this to be configurable open an issue. +If you do not add a QoS section QoS will not be used however Ocelot will default to a 90 second timeout on all downstream requests. If someone needs this to be configurable open an issue. diff --git a/docs/features/raft.rst b/docs/features/raft.rst index fdbc1ea2..69b80404 100644 --- a/docs/features/raft.rst +++ b/docs/features/raft.rst @@ -1,49 +1,49 @@ -Raft (EXPERIMENTAL DO NOT USE IN PRODUCTION) -============================================ - -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 algorithm that allows a cluster of servers (Ocelots) to maintain local state without having a centralised database for storing state (e.g. SQL Server). - -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 - - public virtual void ConfigureServices(IServiceCollection services) - { - services - .AddOcelot() - .AddAdministration("/administration", "secret") - .AddRafty(); - } - -In addition to this you must add a file called peers.json to your main project and it will look as follows - -.. code-block:: json - - { - "Peers": [{ - "HostAndPort": "http://localhost:5000" - }, - { - "HostAndPort": "http://localhost:5002" - }, - { - "HostAndPort": "http://localhost:5003" - }, - { - "HostAndPort": "http://localhost:5004" - }, - { - "HostAndPort": "http://localhost:5001" - } - ] - } - -Each instance of Ocelot must have it's address in the array so that they can communicate using Rafty. - -Once you have made these configuration changes you must deploy and start each instance of Ocelot using the addresses in the peers.json file. The servers should then start communicating with each other! You can test if everything is working by posting a configuration update and checking it has replicated to all servers by getting their configuration. +Raft (EXPERIMENTAL DO NOT USE IN PRODUCTION) +============================================ + +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 algorithm that allows a cluster of servers (Ocelots) to maintain local state without having a centralised database for storing state (e.g. SQL Server). + +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 + + public virtual void ConfigureServices(IServiceCollection services) + { + services + .AddOcelot() + .AddAdministration("/administration", "secret") + .AddRafty(); + } + +In addition to this you must add a file called peers.json to your main project and it will look as follows + +.. code-block:: json + + { + "Peers": [{ + "HostAndPort": "http://localhost:5000" + }, + { + "HostAndPort": "http://localhost:5002" + }, + { + "HostAndPort": "http://localhost:5003" + }, + { + "HostAndPort": "http://localhost:5004" + }, + { + "HostAndPort": "http://localhost:5001" + } + ] + } + +Each instance of Ocelot must have it's address in the array so that they can communicate using Rafty. + +Once you have made these configuration changes you must deploy and start each instance of Ocelot using the addresses in the peers.json file. The servers should then start communicating with each other! You can test if everything is working by posting a configuration update and checking it has replicated to all servers by getting their configuration. diff --git a/docs/features/ratelimiting.rst b/docs/features/ratelimiting.rst index 4dd0354f..ee0abb5e 100644 --- a/docs/features/ratelimiting.rst +++ b/docs/features/ratelimiting.rst @@ -1,47 +1,47 @@ -Rate Limiting -============= - -Thanks to `@catcherwong article `_ for inspiring me to finally write this documentation. - -Ocelot supports rate limiting of upstream requests so that your downstream services do not become overloaded. This feature was added by @geffzhang on GitHub! Thanks very much. - -OK so to get rate limiting working for a ReRoute you need to add the following json to it. - -.. code-block:: json - - "RateLimitOptions": { - "ClientWhitelist": [], - "EnableRateLimiting": true, - "Period": "1s", - "PeriodTimespan": 1, - "Limit": 1 - } - -ClientWhitelist - This is an array that contains the whitelist of the client. It means that the client in this array will not be affected by the rate limiting. - -EnableRateLimiting - This value specifies enable endpoint rate limiting. - -Period - This value specifies the period that the limit applies to, such as 1s, 5m, 1h,1d and so on. If you make more requests in the period than the limit allows then you need to wait for PeriodTimespan to elapse before you make another request. - -PeriodTimespan - This value specifies that we can retry after a certain number of seconds. - -Limit - This value specifies the maximum number of requests that a client can make in a defined period. - -You can also set the following in the GlobalConfiguration part of ocelot.json - -.. code-block:: json - - "RateLimitOptions": { - "DisableRateLimitHeaders": false, - "QuotaExceededMessage": "Customize Tips!", - "HttpStatusCode": 999, - "ClientIdHeader" : "Test" - } - -DisableRateLimitHeaders - This value specifies whether X-Rate-Limit and Retry-After headers are disabled. - -QuotaExceededMessage - This value specifies the exceeded message. - -HttpStatusCode - This value specifies the returned HTTP Status code when rate limiting occurs. - -ClientIdHeader - Allows you to specifiy the header that should be used to identify clients. By default it is "ClientId" +Rate Limiting +============= + +Thanks to `@catcherwong article `_ for inspiring me to finally write this documentation. + +Ocelot supports rate limiting of upstream requests so that your downstream services do not become overloaded. This feature was added by @geffzhang on GitHub! Thanks very much. + +OK so to get rate limiting working for a ReRoute you need to add the following json to it. + +.. code-block:: json + + "RateLimitOptions": { + "ClientWhitelist": [], + "EnableRateLimiting": true, + "Period": "1s", + "PeriodTimespan": 1, + "Limit": 1 + } + +ClientWhitelist - This is an array that contains the whitelist of the client. It means that the client in this array will not be affected by the rate limiting. + +EnableRateLimiting - This value specifies enable endpoint rate limiting. + +Period - This value specifies the period that the limit applies to, such as 1s, 5m, 1h,1d and so on. If you make more requests in the period than the limit allows then you need to wait for PeriodTimespan to elapse before you make another request. + +PeriodTimespan - This value specifies that we can retry after a certain number of seconds. + +Limit - This value specifies the maximum number of requests that a client can make in a defined period. + +You can also set the following in the GlobalConfiguration part of ocelot.json + +.. code-block:: json + + "RateLimitOptions": { + "DisableRateLimitHeaders": false, + "QuotaExceededMessage": "Customize Tips!", + "HttpStatusCode": 999, + "ClientIdHeader" : "Test" + } + +DisableRateLimitHeaders - This value specifies whether X-Rate-Limit and Retry-After headers are disabled. + +QuotaExceededMessage - This value specifies the exceeded message. + +HttpStatusCode - This value specifies the returned HTTP Status code when rate limiting occurs. + +ClientIdHeader - Allows you to specifiy the header that should be used to identify clients. By default it is "ClientId" diff --git a/docs/features/requestaggregation.rst b/docs/features/requestaggregation.rst index 6281728c..0b1a1e5b 100644 --- a/docs/features/requestaggregation.rst +++ b/docs/features/requestaggregation.rst @@ -1,187 +1,185 @@ -Request Aggregation -=================== - -Ocelot allows you to specify Aggregate ReRoutes that compose multiple normal ReRoutes and map their responses into one object. This is usually where you have -a client that is making multiple requests to a server where it could just be one. This feature allows you to start implementing back end for a front end type -architecture with Ocelot. - -This feature was requested as part of `Issue 79 `_ and further improvements were made as part of `Issue 298 `_. - -In order to set this up you must do something like the following in your ocelot.json. Here we have specified two normal ReRoutes and each one has a Key property. -We then specify an Aggregate that composes the two ReRoutes using their keys in the ReRouteKeys list and says then we have the UpstreamPathTemplate which works like a normal ReRoute. -Obviously you cannot have duplicate UpstreamPathTemplates between ReRoutes and Aggregates. You can use all of Ocelot's normal ReRoute options apart from RequestIdKey (explained in gotchas below). - -Advanced register your own Aggregators -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -Ocelot started with just the basic request aggregation and since then we have added a more advanced method that let's the user take in the responses from the -downstream services and then aggregate them into a response object. - -The ocelot.json setup is pretty much the same as the basic aggregation approach apart from you need to add an Aggregator property like below. - -.. code-block:: json - - { - "ReRoutes": [ - { - "DownstreamPathTemplate": "/", - "UpstreamPathTemplate": "/laura", - "UpstreamHttpMethod": [ - "Get" - ], - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 51881 - } - ], - "Key": "Laura" - }, - { - "DownstreamPathTemplate": "/", - "UpstreamPathTemplate": "/tom", - "UpstreamHttpMethod": [ - "Get" - ], - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 51882 - } - ], - "Key": "Tom" - } - ], - "Aggregates": [ - { - "ReRouteKeys": [ - "Tom", - "Laura" - ], - "UpstreamPathTemplate": "/", - "Aggregator": "FakeDefinedAggregator" - } - ] - } - -Here we have added an aggregator called FakeDefinedAggregator. Ocelot is going to look for this aggregator when it tries to aggregate this ReRoute. - -In order to make the aggregator available we must add the FakeDefinedAggregator to the OcelotBuilder like below. - -.. code-block:: csharp - - services - .AddOcelot() - .AddSingletonDefinedAggregator(); - -Now when Ocelot tries to aggregate the ReRoute above it will find the FakeDefinedAggregator in the container and use it to aggregate the ReRoute. -Because the FakeDefinedAggregator is registered in the container you can add any dependencies it needs into the container like below. - -.. code-block:: csharp - - services.AddSingleton(); - - services - .AddOcelot() - .AddSingletonDefinedAggregator(); - -In this example FooAggregator takes a dependency on FooDependency and it will be resolved by the container. - -In addition to this Ocelot lets you add transient aggregators like below. - -.. code-block:: csharp - - services - .AddOcelot() - .AddTransientDefinedAggregator(); - -In order to make an Aggregator you must implement this interface. - -.. code-block:: csharp - - public interface IDefinedAggregator - { - Task Aggregate(List responses); - } - -With this feature you can pretty much do whatever you want because DownstreamResponse contains Content, Headers and Status Code. We can add extra things if needed -just raise an issue on GitHub. Please note if the HttpClient throws an exception when making a request to a ReRoute in the aggregate then you will not get a DownstreamResponse for -it but you would for any that succeed. If it does throw an exception this will be logged. - -Basic expecting JSON from Downstream Services -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: json - - { - "ReRoutes": [ - { - "DownstreamPathTemplate": "/", - "UpstreamPathTemplate": "/laura", - "UpstreamHttpMethod": [ - "Get" - ], - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 51881 - } - ], - "Key": "Laura" - }, - { - "DownstreamPathTemplate": "/", - "UpstreamPathTemplate": "/tom", - "UpstreamHttpMethod": [ - "Get" - ], - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 51882 - } - ], - "Key": "Tom" - } - ], - "Aggregates": [ - { - "ReRouteKeys": [ - "Tom", - "Laura" - ], - "UpstreamPathTemplate": "/" - } - ] - } - -You can also set UpstreamHost and ReRouteIsCaseSensitive in the Aggregate configuration. These behave the same as any other ReRoutes. - -If the ReRoute /tom returned a body of {"Age": 19} and /laura returned {"Age": 25} the the response after aggregation would be as follows. - -.. code-block:: json - - {"Tom":{"Age": 19},"Laura":{"Age": 25}} - -At the moment the aggregation is very simple. Ocelot just gets the response from your downstream service and sticks it into a json dictionary -as above. With the ReRoute key being the key of the dictionary and the value the response body from your downstream service. You can see that the object is just -JSON without any pretty spaces etc. - -All headers will be lost from the downstream services response. - -Ocelot will always return content type application/json with an aggregate request. - -If you downstream services return a 404 the aggregate will just return nothing for that downstream service. -It will not change the aggregate response into a 404 even if all the downstreams return a 404. - -Gotcha's / Further info ------------------------ - -You cannot use ReRoutes with specific RequestIdKeys as this would be crazy complicated to track. - -Aggregation only supports the GET HTTP Verb. - +Request Aggregation +=================== + +Ocelot allows you to specify Aggregate ReRoutes that compose multiple normal ReRoutes and map their responses into one object. This is usually where you have +a client that is making multiple requests to a server where it could just be one. This feature allows you to start implementing back end for a front end type +architecture with Ocelot. + +This feature was requested as part of `Issue 79 `_ and further improvements were made as part of `Issue 298 `_. + +In order to set this up you must do something like the following in your ocelot.json. Here we have specified two normal ReRoutes and each one has a Key property. +We then specify an Aggregate that composes the two ReRoutes using their keys in the ReRouteKeys list and says then we have the UpstreamPathTemplate which works like a normal ReRoute. +Obviously you cannot have duplicate UpstreamPathTemplates between ReRoutes and Aggregates. You can use all of Ocelot's normal ReRoute options apart from RequestIdKey (explained in gotchas below). + +Advanced register your own Aggregators +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Ocelot started with just the basic request aggregation and since then we have added a more advanced method that let's the user take in the responses from the +downstream services and then aggregate them into a response object. + +The ocelot.json setup is pretty much the same as the basic aggregation approach apart from you need to add an Aggregator property like below. + +.. code-block:: json + + { + "ReRoutes": [ + { + "DownstreamPathTemplate": "/", + "UpstreamPathTemplate": "/laura", + "UpstreamHttpMethod": [ + "Get" + ], + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 51881 + } + ], + "Key": "Laura" + }, + { + "DownstreamPathTemplate": "/", + "UpstreamPathTemplate": "/tom", + "UpstreamHttpMethod": [ + "Get" + ], + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 51882 + } + ], + "Key": "Tom" + } + ], + "Aggregates": [ + { + "ReRouteKeys": [ + "Tom", + "Laura" + ], + "UpstreamPathTemplate": "/", + "Aggregator": "FakeDefinedAggregator" + } + ] + } + +Here we have added an aggregator called FakeDefinedAggregator. Ocelot is going to look for this aggregator when it tries to aggregate this ReRoute. + +In order to make the aggregator available we must add the FakeDefinedAggregator to the OcelotBuilder like below. + +.. code-block:: csharp + + services + .AddOcelot() + .AddSingletonDefinedAggregator(); + +Now when Ocelot tries to aggregate the ReRoute above it will find the FakeDefinedAggregator in the container and use it to aggregate the ReRoute. +Because the FakeDefinedAggregator is registered in the container you can add any dependencies it needs into the container like below. + +.. code-block:: csharp + + services.AddSingleton(); + + services + .AddOcelot() + .AddSingletonDefinedAggregator(); + +In this example FooAggregator takes a dependency on FooDependency and it will be resolved by the container. + +In addition to this Ocelot lets you add transient aggregators like below. + +.. code-block:: csharp + + services + .AddOcelot() + .AddTransientDefinedAggregator(); + +In order to make an Aggregator you must implement this interface. + +.. code-block:: csharp + + public interface IDefinedAggregator + { + Task Aggregate(List responses); + } + +With this feature you can pretty much do whatever you want because the HttpContext objects contain the results of all the aggregate requests. Please note if the HttpClient throws an exception when making a request to a ReRoute in the aggregate then you will not get a HttpContext for it but you would for any that succeed. If it does throw an exception this will be logged. + +Basic expecting JSON from Downstream Services +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. code-block:: json + + { + "ReRoutes": [ + { + "DownstreamPathTemplate": "/", + "UpstreamPathTemplate": "/laura", + "UpstreamHttpMethod": [ + "Get" + ], + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 51881 + } + ], + "Key": "Laura" + }, + { + "DownstreamPathTemplate": "/", + "UpstreamPathTemplate": "/tom", + "UpstreamHttpMethod": [ + "Get" + ], + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 51882 + } + ], + "Key": "Tom" + } + ], + "Aggregates": [ + { + "ReRouteKeys": [ + "Tom", + "Laura" + ], + "UpstreamPathTemplate": "/" + } + ] + } + +You can also set UpstreamHost and ReRouteIsCaseSensitive in the Aggregate configuration. These behave the same as any other ReRoutes. + +If the ReRoute /tom returned a body of {"Age": 19} and /laura returned {"Age": 25} the the response after aggregation would be as follows. + +.. code-block:: json + + {"Tom":{"Age": 19},"Laura":{"Age": 25}} + +At the moment the aggregation is very simple. Ocelot just gets the response from your downstream service and sticks it into a json dictionary +as above. With the ReRoute key being the key of the dictionary and the value the response body from your downstream service. You can see that the object is just +JSON without any pretty spaces etc. + +All headers will be lost from the downstream services response. + +Ocelot will always return content type application/json with an aggregate request. + +If you downstream services return a 404 the aggregate will just return nothing for that downstream service. +It will not change the aggregate response into a 404 even if all the downstreams return a 404. + +Gotcha's / Further info +----------------------- + +You cannot use ReRoutes with specific RequestIdKeys as this would be crazy complicated to track. + +Aggregation only supports the GET HTTP Verb. + diff --git a/docs/features/requestid.rst b/docs/features/requestid.rst index fb3f74cf..a723a4b7 100644 --- a/docs/features/requestid.rst +++ b/docs/features/requestid.rst @@ -1,12 +1,10 @@ Request Id / Correlation Id =========================== -Ocelot supports a client sending a request id in the form of a header. If set Ocelot will -use the requestid for logging as soon as it becomes available in the middleware pipeline. +Ocelot supports a client sending a request id in the form of a header. If set Ocelot willuse the requestid for logging as soon as it becomes available in the middleware pipeline. Ocelot will also forward the request id with the specified header to the downstream service. -You can still get the asp.net core request id in the logs if you set -IncludeScopes true in your logging config. +You can still get the asp.net core request id in the logs if you set IncludeScopes true in your logging config. In order to use the request id feature you have two options. diff --git a/docs/features/routing.rst b/docs/features/routing.rst index 18cc60c1..50399b1b 100644 --- a/docs/features/routing.rst +++ b/docs/features/routing.rst @@ -1,245 +1,238 @@ -Routing -======= - -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 -anything working in Ocelot you need to set up a ReRoute in the configuration. - -.. code-block:: json - - { - "ReRoutes": [ - ] - } - -To configure a ReRoute you need to add one to the ReRoutes json array. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 80, - } - ], - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": [ "Put", "Delete" ] - } - -The DownstreamPathTemplate, DownstreamScheme and DownstreamHostAndPorts define the URL that a request will be forwarded to. - -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 identify 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. - -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. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/api/{everything}", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 80, - } - ], - "UpstreamPathTemplate": "/{everything}", - "UpstreamHttpMethod": [ "Get", "Post" ] - } - -This will forward any path + query string combinations to the downstream service after the path /api. - - -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 - - "ReRouteIsCaseSensitive": true - -This means that when Ocelot tries to match the incoming upstream url with an upstream template the -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, all requests will be proxied straight through. The placeholder {url} name is not significant, any name will work. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/{url}", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 80, - } - ], - "UpstreamPathTemplate": "/{url}", - "UpstreamHttpMethod": [ "Get" ] - } - -The catch all has a lower priority than any other ReRoute. If you also have the ReRoute below in your config then Ocelot would match it before the catch all. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "10.0.10.1", - "Port": 80, - } - ], - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": [ "Get" ] - } - -Upstream Host -^^^^^^^^^^^^^ - -This feature allows you to have ReRoutes based on the upstream host. This works by looking at the host header the client has used and then using this as part of the information we use to identify a ReRoute. - -In order to use this feature please add the following to your config. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/", - "DownstreamScheme": "https", - "DownstreamHostAndPorts": [ - { - "Host": "10.0.10.1", - "Port": 80, - } - ], - "UpstreamPathTemplate": "/", - "UpstreamHttpMethod": [ "Get" ], - "UpstreamHost": "somedomain.com" - } - -The ReRoute above will only be matched when the host header value is somedomain.com. - -If you do not set UpstreamHost on a 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 -^^^^^^^^ - -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 - - { - "Priority": 0 - } - -0 is the lowest priority, Ocelot will always use 0 for /{catchAll} ReRoutes and this is still hardcoded. After that you are free -to set any priority you wish. - -e.g. you could have - -.. code-block:: json - - { - "UpstreamPathTemplate": "/goods/{catchAll}" - "Priority": 0 - } - -and - -.. code-block:: json - - { - "UpstreamPathTemplate": "/goods/delete" - "Priority": 1 - } - -In the example above if you make a request into Ocelot on /goods/delete Ocelot will match /goods/delete ReRoute. Previously it would have -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 sounds interesting to you. - -Query Strings -^^^^^^^^^^^^^ - -Ocelot allows you to specify a query string as part of the DownstreamPathTemplate like the example below. - -.. code-block:: json - - { - "ReRoutes": [ - { - "DownstreamPathTemplate": "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", - "UpstreamPathTemplate": "/api/units/{subscriptionId}/{unitId}/updates", - "UpstreamHttpMethod": [ - "Get" - ], - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 50110 - } - ] - } - ], - "GlobalConfiguration": { - } - } - -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 parameters in the UpstreamPathTemplate so you can match certain queries to certain services. - -.. code-block:: json - - { - "ReRoutes": [ - { - "DownstreamPathTemplate": "/api/units/{subscriptionId}/{unitId}/updates", - "UpstreamPathTemplate": "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", - "UpstreamHttpMethod": [ - "Get" - ], - "DownstreamScheme": "http", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 50110 - } - ] - } - ], - "GlobalConfiguration": { - } - } - -In this example Ocelot will only match requests that have a matching url path and the query string starts with unitId=something. You can have other queries after this -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. +Routing +======= + +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 anything working in Ocelot you need to set up a ReRoute in the configuration. + +.. code-block:: json + + { + "ReRoutes": [ + ] + } + +To configure a ReRoute you need to add one to the ReRoutes json array. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 80, + } + ], + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Put", "Delete" ] + } + +The DownstreamPathTemplate, DownstreamScheme and DownstreamHostAndPorts define the URL that a request will be forwarded to. + +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 identify 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. + +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. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/{everything}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 80, + } + ], + "UpstreamPathTemplate": "/{everything}", + "UpstreamHttpMethod": [ "Get", "Post" ] + } + +This will forward any path + query string combinations to the downstream service after the path /api. + + +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 + + "ReRouteIsCaseSensitive": true + +This means that when Ocelot tries to match the incoming upstream url with an upstream template the +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, all requests will be proxied straight through. The placeholder {url} name is not significant, any name will work. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/{url}", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 80, + } + ], + "UpstreamPathTemplate": "/{url}", + "UpstreamHttpMethod": [ "Get" ] + } + +The catch all has a lower priority than any other ReRoute. If you also have the ReRoute below in your config then Ocelot would match it before the catch all. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "10.0.10.1", + "Port": 80, + } + ], + "UpstreamPathTemplate": "/", + "UpstreamHttpMethod": [ "Get" ] + } + +Upstream Host +^^^^^^^^^^^^^ + +This feature allows you to have ReRoutes based on the upstream host. This works by looking at the host header the client has used and then using this as part of the information we use to identify a ReRoute. + +In order to use this feature please add the following to your config. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/", + "DownstreamScheme": "https", + "DownstreamHostAndPorts": [ + { + "Host": "10.0.10.1", + "Port": 80, + } + ], + "UpstreamPathTemplate": "/", + "UpstreamHttpMethod": [ "Get" ], + "UpstreamHost": "somedomain.com" + } + +The ReRoute above will only be matched when the host header value is somedomain.com. + +If you do not set UpstreamHost on a 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 +^^^^^^^^ + +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 + + { + "Priority": 0 + } + +0 is the lowest priority, Ocelot will always use 0 for /{catchAll} ReRoutes and this is still hardcoded. After that you are free to set any priority you wish. + +e.g. you could have + +.. code-block:: json + + { + "UpstreamPathTemplate": "/goods/{catchAll}" + "Priority": 0 + } + +and + +.. code-block:: json + + { + "UpstreamPathTemplate": "/goods/delete" + "Priority": 1 + } + +In the example above if you make a request into Ocelot on /goods/delete Ocelot will match /goods/delete ReRoute. Previously it would have 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 sounds interesting to you. + +Query Strings +^^^^^^^^^^^^^ + +Ocelot allows you to specify a query string as part of the DownstreamPathTemplate like the example below. + +.. code-block:: json + + { + "ReRoutes": [ + { + "DownstreamPathTemplate": "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", + "UpstreamPathTemplate": "/api/units/{subscriptionId}/{unitId}/updates", + "UpstreamHttpMethod": [ + "Get" + ], + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 50110 + } + ] + } + ], + "GlobalConfiguration": { + } + } + +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 parameters in the UpstreamPathTemplate so you can match certain queries to certain services. + +.. code-block:: json + + { + "ReRoutes": [ + { + "DownstreamPathTemplate": "/api/units/{subscriptionId}/{unitId}/updates", + "UpstreamPathTemplate": "/api/subscriptions/{subscriptionId}/updates?unitId={unitId}", + "UpstreamHttpMethod": [ + "Get" + ], + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 50110 + } + ] + } + ], + "GlobalConfiguration": { + } + } + +In this example Ocelot will only match requests that have a matching url path and the query string starts with unitId=something. You can have other queries after this +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. diff --git a/docs/features/servicediscovery.rst b/docs/features/servicediscovery.rst index 2552a6b2..85a0dec5 100644 --- a/docs/features/servicediscovery.rst +++ b/docs/features/servicediscovery.rst @@ -1,255 +1,244 @@ -.. service-discovery: - -Service Discovery -================= - -Ocelot allows you to specify a service discovery provider and will use this to find the host and port -for the downstream service Ocelot is forwarding a request to. At the moment this is only supported in the -GlobalConfiguration section which means the same service discovery provider will be used for all ReRoutes -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. - -Please note the Scheme option defauls to HTTP. It was added in this `PR `_. It defaults to HTTP to not introduce a breaking change. - -.. code-block:: json - - "ServiceDiscoveryProvider": { - "Scheme": "https", - "Host": "localhost", - "Port": 8500, - "Type": "Consul" - } - -In the future we can add a feature that allows ReRoute specfic configuration. - -In order to tell Ocelot a ReRoute is to use the service discovery provider for its host and port you must add the -ServiceName and load balancer you wish to use when making requests downstream. At the moment Ocelot has a RoundRobin -and LeastConnection algorithm you can use. If no load balancer is specified Ocelot will not load balance requests. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/api/posts/{postId}", - "DownstreamScheme": "https", - "UpstreamPathTemplate": "/posts/{postId}", - "UpstreamHttpMethod": [ "Put" ], - "ServiceName": "product", - "LoadBalancerOptions": { - "Type": "LeastConnection" - }, - } - -When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. - -A lot of people have asked me to implement a feature where Ocelot polls Consul for latest service information rather than per request. If you want to poll Consul for the latest services rather than per request (default behaviour) then you need to set the following configuration. - -.. code-block:: json - - "ServiceDiscoveryProvider": { - "Host": "localhost", - "Port": 8500, - "Type": "PollConsul", - "PollingInterval": 100 - } - -The polling interval is in milliseconds and tells Ocelot how often to call Consul for changes in service configuration. - -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 volatile 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. - -Your 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 ---------- - -If you are using ACL with Consul Ocelot supports adding the X-Consul-Token header. In order so this to work you must add the additional property below. - -.. code-block:: json - - "ServiceDiscoveryProvider": { - "Host": "localhost", - "Port": 8500, - "Token": "footoken", - "Type": "Consul" - } - -Ocelot will add this token to the Consul client that it uses to make requests and that is then used for every request. - -Eureka -^^^^^^ - -This feature was requested as part of `Issue 262 `_ . to add support for Netflix's -Eureka service discovery provider. The main reason for this is it is a key part of `Steeltoe `_ which is something -to do with `Pivotal `_! Anyway enough of the background. - -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 - - "ServiceDiscoveryProvider": { - "Type": "Eureka" - } - -And following the guide `Here `_ you may also need to add some stuff to appsettings.json. For example the json below tells the steeltoe / pivotal services where to look for the service discovery server and if the service should register with it. - -.. code-block:: json - - "eureka": { - "client": { - "serviceUrl": "http://localhost:8761/eureka/", - "shouldRegisterWithEureka": false, - "shouldFetchRegistry": true - } - } - -I am told that if shouldRegisterWithEureka is false then shouldFetchRegistry will defaut to true so you don't need it explicitly but left it in there. - -Ocelot will now register all the necessary services when it starts up and if you have the json above will register itself with -Eureka. One of the services polls Eureka every 30 seconds (default) and gets the latest service state and persists this in memory. -When Ocelot asks for a given service it is retrieved from memory so performance is not a big problem. Please note that this code -is provided by the Pivotal.Discovery.Client NuGet package so big thanks to them for all the hard work. - -Ocelot will use the scheme (http/https) set in Eureka if these values are not provided in ocelot.json - -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 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. - -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. - -The config might look something like - -.. code-block:: json - - { - "ReRoutes": [], - "Aggregates": [], - "GlobalConfiguration": { - "RequestIdKey": null, - "ServiceDiscoveryProvider": { - "Host": "localhost", - "Port": 8500, - "Type": "Consul", - "Token": null, - "ConfigurationKey": null - }, - "RateLimitOptions": { - "ClientIdHeader": "ClientId", - "QuotaExceededMessage": null, - "RateLimitCounterPrefix": "ocelot", - "DisableRateLimitHeaders": false, - "HttpStatusCode": 429 - }, - "QoSOptions": { - "ExceptionsAllowedBeforeBreaking": 0, - "DurationOfBreak": 0, - "TimeoutValue": 0 - }, - "BaseUrl": null, - "LoadBalancerOptions": { - "Type": "LeastConnection", - "Key": null, - "Expiry": 0 - }, - "DownstreamScheme": "http", - "HttpHandlerOptions": { - "AllowAutoRedirect": false, - "UseCookieContainer": false, - "UseTracing": false - } - } - } - -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, - "Type": "Consul" - }, - "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. +.. service-discovery: + +Service Discovery +================= + +Ocelot allows you to specify a service discovery provider and will use this to find the host and port for the downstream service Ocelot is forwarding a request to. At the moment this is only supported in the +GlobalConfiguration section which means the same service discovery provider will be used for all ReRoutes 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. + +Please note the Scheme option defauls to HTTP. It was added in this `PR `_. It defaults to HTTP to not introduce a breaking change. + +.. code-block:: json + + "ServiceDiscoveryProvider": { + "Scheme": "https", + "Host": "localhost", + "Port": 8500, + "Type": "Consul" + } + +In the future we can add a feature that allows ReRoute specfic configuration. + +In order to tell Ocelot a ReRoute is to use the service discovery provider for its host and port you must add the ServiceName and load balancer you wish to use when making requests downstream. At the moment Ocelot has a RoundRobin and LeastConnection algorithm you can use. If no load balancer is specified Ocelot will not load balance requests. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/api/posts/{postId}", + "DownstreamScheme": "https", + "UpstreamPathTemplate": "/posts/{postId}", + "UpstreamHttpMethod": [ "Put" ], + "ServiceName": "product", + "LoadBalancerOptions": { + "Type": "LeastConnection" + }, + } + +When this is set up Ocelot will lookup the downstream host and port from the service discover provider and load balance requests across any available services. + +A lot of people have asked me to implement a feature where Ocelot polls Consul for latest service information rather than per request. If you want to poll Consul for the latest services rather than per request (default behaviour) then you need to set the following configuration. + +.. code-block:: json + + "ServiceDiscoveryProvider": { + "Host": "localhost", + "Port": 8500, + "Type": "PollConsul", + "PollingInterval": 100 + } + +The polling interval is in milliseconds and tells Ocelot how often to call Consul for changes in service configuration. + +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 volatile 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. + +Your 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 +--------- + +If you are using ACL with Consul Ocelot supports adding the X-Consul-Token header. In order so this to work you must add the additional property below. + +.. code-block:: json + + "ServiceDiscoveryProvider": { + "Host": "localhost", + "Port": 8500, + "Token": "footoken", + "Type": "Consul" + } + +Ocelot will add this token to the Consul client that it uses to make requests and that is then used for every request. + +Eureka +^^^^^^ + +This feature was requested as part of `Issue 262 `_ . to add support for Netflix's Eureka service discovery provider. The main reason for this is it is a key part of `Steeltoe `_ which is something to do with `Pivotal `_! Anyway enough of the background. + +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 + + "ServiceDiscoveryProvider": { + "Type": "Eureka" + } + +And following the guide `Here `_ you may also need to add some stuff to appsettings.json. For example the json below tells the steeltoe / pivotal services where to look for the service discovery server and if the service should register with it. + +.. code-block:: json + + "eureka": { + "client": { + "serviceUrl": "http://localhost:8761/eureka/", + "shouldRegisterWithEureka": false, + "shouldFetchRegistry": true + } + } + +I am told that if shouldRegisterWithEureka is false then shouldFetchRegistry will defaut to true so you don't need it explicitly but left it in there. + +Ocelot will now register all the necessary services when it starts up and if you have the json above will register itself with Eureka. One of the services polls Eureka every 30 seconds (default) and gets the latest service state and persists this in memory. When Ocelot asks for a given service it is retrieved from memory so performance is not a big problem. Please note that this code is provided by the Pivotal.Discovery.Client NuGet package so big thanks to them for all the hard work. + +Ocelot will use the scheme (http/https) set in Eureka if these values are not provided in ocelot.json + +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 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. + +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. + +The config might look something like + +.. code-block:: json + + { + "ReRoutes": [], + "Aggregates": [], + "GlobalConfiguration": { + "RequestIdKey": null, + "ServiceDiscoveryProvider": { + "Host": "localhost", + "Port": 8500, + "Type": "Consul", + "Token": null, + "ConfigurationKey": null + }, + "RateLimitOptions": { + "ClientIdHeader": "ClientId", + "QuotaExceededMessage": null, + "RateLimitCounterPrefix": "ocelot", + "DisableRateLimitHeaders": false, + "HttpStatusCode": 429 + }, + "QoSOptions": { + "ExceptionsAllowedBeforeBreaking": 0, + "DurationOfBreak": 0, + "TimeoutValue": 0 + }, + "BaseUrl": null, + "LoadBalancerOptions": { + "Type": "LeastConnection", + "Key": null, + "Expiry": 0 + }, + "DownstreamScheme": "http", + "HttpHandlerOptions": { + "AllowAutoRedirect": false, + "UseCookieContainer": false, + "UseTracing": false + } + } + } + +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, + "Type": "Consul" + }, + "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/servicefabric.rst b/docs/features/servicefabric.rst index 0bc3ee1b..29b473aa 100644 --- a/docs/features/servicefabric.rst +++ b/docs/features/servicefabric.rst @@ -1,42 +1,40 @@ -Service Fabric -============== - -If you have services deployed in Service Fabric you will normally use the naming service to access them. - -The following example shows how to set up a ReRoute that will work in Service Fabric. The most important thing is the ServiceName which is made up of the -Service Fabric application name then the specific service name. We also need to set up the ServiceDiscoveryProvider in -GlobalConfiguration. The example here shows a typical configuration. It assumes service fabric is running on localhost and that the naming service is on port 19081. - -The example below is taken from the samples folder so please check it if this doesnt make sense! - -.. code-block:: json - - { - "ReRoutes": [ - { - "DownstreamPathTemplate": "/api/values", - "UpstreamPathTemplate": "/EquipmentInterfaces", - "UpstreamHttpMethod": [ - "Get" - ], - "DownstreamScheme": "http", - "ServiceName": "OcelotServiceApplication/OcelotApplicationService", - } - ], - "GlobalConfiguration": { - "RequestIdKey": "OcRequestId", - "ServiceDiscoveryProvider": { - "Host": "localhost", - "Port": 19081, - "Type": "ServiceFabric" - } - } - } - -If you are using stateless / guest exe services ocelot will be able to proxy through the naming service without anything else. However -if you are using statefull / actor services you must send the PartitionKind and PartitionKey query string values with the client -request e.g. - -GET http://ocelot.com/EquipmentInterfaces?PartitionKind=xxx&PartitionKey=xxx - -There is no way for Ocelot to work these out for you. +Service Fabric +============== + +If you have services deployed in Service Fabric you will normally use the naming service to access them. + +The following example shows how to set up a ReRoute that will work in Service Fabric. The most important thing is the ServiceName which is made up of the Service Fabric application name then the specific service name. We also need to set up the ServiceDiscoveryProvider in GlobalConfiguration. The example here shows a typical configuration. It assumes service fabric is running on localhost and that the naming service is on port 19081. + +The example below is taken from the samples folder so please check it if this doesnt make sense! + +.. code-block:: json + + { + "ReRoutes": [ + { + "DownstreamPathTemplate": "/api/values", + "UpstreamPathTemplate": "/EquipmentInterfaces", + "UpstreamHttpMethod": [ + "Get" + ], + "DownstreamScheme": "http", + "ServiceName": "OcelotServiceApplication/OcelotApplicationService", + } + ], + "GlobalConfiguration": { + "RequestIdKey": "OcRequestId", + "ServiceDiscoveryProvider": { + "Host": "localhost", + "Port": 19081, + "Type": "ServiceFabric" + } + } + } + +If you are using stateless / guest exe services ocelot will be able to proxy through the naming service without anything else. However +if you are using statefull / actor services you must send the PartitionKind and PartitionKey query string values with the client +request e.g. + +GET http://ocelot.com/EquipmentInterfaces?PartitionKind=xxx&PartitionKey=xxx + +There is no way for Ocelot to work these out for you. diff --git a/docs/features/websockets.rst b/docs/features/websockets.rst index 5b587919..3d63d3d4 100644 --- a/docs/features/websockets.rst +++ b/docs/features/websockets.rst @@ -1,114 +1,109 @@ -Websockets -========== - -Ocelot supports proxying websockets with some extra bits. This functionality was requested in `Issue 212 `_. - -In order to get websocket proxying working with Ocelot you need to do the following. - -In your Configure method you need to tell your application to use WebSockets. - -.. code-block:: csharp - - Configure(app => - { - app.UseWebSockets(); - app.UseOcelot().Wait(); - }) - -Then in your ocelot.json add the following to proxy a ReRoute using websockets. - -.. code-block:: json - - { - "DownstreamPathTemplate": "/ws", - "UpstreamPathTemplate": "/", - "DownstreamScheme": "ws", - "DownstreamHostAndPorts": [ - { - "Host": "localhost", - "Port": 5001 - } - ], - } - -With this configuration set Ocelot will match any websocket traffic that comes in on / and proxy it to localhost:5001/ws. To make this clearer -Ocelot will receive messages from the upstream client, proxy these to the downstream service, receive messages from the downstream service and -proxy these to the upstream client. - -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 -^^^^^^^^^ - -1. Load Balancer -2. Routing -3. Service Discovery - -This means that you can set up your downstream services running websockets and either have multiple DownstreamHostAndPorts in your ReRoute -config or hook your ReRoute into a service discovery provider and then load balance requests...Which I think is pretty cool :) - -Not Supported -^^^^^^^^^^^^^ - -Unfortunately a lot of Ocelot's features are non websocket specific such as header and http client stuff. I've listed what won't work below. - -1. Tracing -2. RequestId -3. Request Aggregation -4. Rate Limiting -5. Quality of Service -6. Middleware Injection -7. Header Transformation -8. Delegating Handlers -9. Claims Transformation -10. Caching -11. Authentication - If anyone requests it we might be able to do something with basic authentication. -12. Authorisation - -I'm not 100% sure what will happen with this feature when it get's into the wild so please make sure you test thoroughly! - - +Websockets +========== + +Ocelot supports proxying websockets with some extra bits. This functionality was requested in `Issue 212 `_. + +In order to get websocket proxying working with Ocelot you need to do the following. + +In your Configure method you need to tell your application to use WebSockets. + +.. code-block:: csharp + + Configure(app => + { + app.UseWebSockets(); + app.UseOcelot().Wait(); + }) + +Then in your ocelot.json add the following to proxy a ReRoute using websockets. + +.. code-block:: json + + { + "DownstreamPathTemplate": "/ws", + "UpstreamPathTemplate": "/", + "DownstreamScheme": "ws", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 5001 + } + ], + } + +With this configuration set Ocelot will match any websocket traffic that comes in on / and proxy it to localhost:5001/ws. To make this clearer Ocelot will receive messages from the upstream client, proxy these to the downstream service, receive messages from the downstream service and proxy these to the upstream client. + +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 +^^^^^^^^^ + +1. Load Balancer +2. Routing +3. Service Discovery + +This means that you can set up your downstream services running websockets and either have multiple DownstreamHostAndPorts in your ReRoute config or hook your ReRoute into a service discovery provider and then load balance requests...Which I think is pretty cool :) + +Not Supported +^^^^^^^^^^^^^ + +Unfortunately a lot of Ocelot's features are non websocket specific such as header and http client stuff. I've listed what won't work below. + +1. Tracing +2. RequestId +3. Request Aggregation +4. Rate Limiting +5. Quality of Service +6. Middleware Injection +7. Header Transformation +8. Delegating Handlers +9. Claims Transformation +10. Caching +11. Authentication - If anyone requests it we might be able to do something with basic authentication. +12. Authorisation + +I'm not 100% sure what will happen with this feature when it get's into the wild so please make sure you test thoroughly! + + diff --git a/docs/introduction/bigpicture.rst b/docs/introduction/bigpicture.rst index 0ea39e17..ee56f0ef 100644 --- a/docs/introduction/bigpicture.rst +++ b/docs/introduction/bigpicture.rst @@ -1,23 +1,13 @@ Big Picture =========== -Ocelot is aimed at people using .NET running -a micro services / service orientated architecture -that need a unified point of entry into their system. +Ocelot is aimed at people using .NET running a micro services / service orientated architecture that need a unified point of entry into their system. -In particular I want easy integration with -IdentityServer reference and bearer tokens. +In particular I want easy integration with IdentityServer reference and bearer tokens. Ocelot is a bunch of middlewares in a specific order. -Ocelot manipulates the HttpRequest object into a state specified by its configuration until -it reaches a request builder middleware where it creates a HttpRequestMessage object which is -used to make a request to a downstream service. The middleware that makes the request is -the last thing in the Ocelot pipeline. It does not call the next middleware. -The response from the downstream service is stored in a per request scoped repository -and retrieved as the requests goes back up the Ocelot pipeline. There is a piece of middleware -that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. -That is basically it with a bunch of other features. +Ocelot manipulates the HttpRequest object into a state specified by its configuration until it reaches a request builder middleware where it creates a HttpRequestMessage object which is used to make a request to a downstream service. The middleware that makes the request is the last thing in the Ocelot pipeline. It does not call the next middleware. There is a piece of middleware that maps the HttpResponseMessage onto the HttpResponse object and that is returned to the client. That is basically it with a bunch of other features. The following are configurations that you use when deploying Ocelot. diff --git a/docs/introduction/contributing.rst b/docs/introduction/contributing.rst index 535e2227..db05d5d9 100644 --- a/docs/introduction/contributing.rst +++ b/docs/introduction/contributing.rst @@ -1,5 +1,4 @@ Contributing ============ -Pull requests, issues and commentary welcome! No special process just create a request and get in -touch either via gitter or create an issue. \ No newline at end of file +Pull requests, issues and commentary welcome! No special process just create a request and get in touch either via gitter or create an issue. \ No newline at end of file diff --git a/docs/introduction/gettingstarted.rst b/docs/introduction/gettingstarted.rst index 8bcb98a3..d861baa5 100644 --- a/docs/introduction/gettingstarted.rst +++ b/docs/introduction/gettingstarted.rst @@ -52,16 +52,13 @@ If you want some example that actually does something use the following: } } -The most important thing to note here is BaseUrl. Ocelot needs to know the URL it is running under -in order to do Header find & replace and for certain administration configurations. When setting this URL it should be the external URL that clients will see Ocelot running on e.g. If you are running containers Ocelot might run on the url http://123.12.1.1:6543 but has something like nginx in front of it responding on https://api.mybusiness.com. In this case the Ocelot base url should be https://api.mybusiness.com. +The most important thing to note here is BaseUrl. Ocelot needs to know the URL it is running under in order to do Header find & replace and for certain administration configurations. When setting this URL it should be the external URL that clients will see Ocelot running on e.g. If you are running containers Ocelot might run on the url http://123.12.1.1:6543 but has something like nginx in front of it responding on https://api.mybusiness.com. In this case the Ocelot base url should be https://api.mybusiness.com. -If you are using containers and require Ocelot to respond to clients on http://123.12.1.1:6543 -then you can do this, however if you are deploying multiple Ocelot's you will probably want to pass this on the command line in some kind of script. Hopefully whatever scheduler you are using can pass the IP. +If you are using containers and require Ocelot to respond to clients on http://123.12.1.1:6543 then you can do this, however if you are deploying multiple Ocelot's you will probably want to pass this on the command line in some kind of script. Hopefully whatever scheduler you are using can pass the IP. **Program** -Then in your Program.cs you will want to have the following. The main things to note are -AddOcelot() (adds ocelot services), UseOcelot().Wait() (sets up all the Ocelot middleware). +Then in your Program.cs you will want to have the following. The main things to note are AddOcelot() (adds ocelot services), UseOcelot().Wait() (sets up all the Ocelot middleware). .. code-block:: csharp @@ -116,8 +113,7 @@ AddOcelot() (adds ocelot services), UseOcelot().Wait() (sets up all the Ocelot m **Install NuGet package** -Install Ocelot and it's dependecies using nuget. You will need to create a netcoreapp1.0+ projct and bring the package into it. Then follow the Startup below and :doc:`../features/configuration` sections -to get up and running. Please note you will need to choose one of the Ocelot packages from the NuGet feed. +Install Ocelot and it's dependecies using nuget. You will need to create a netcoreapp1.0+ projct and bring the package into it. Then follow the Startup below and :doc:`../features/configuration` sections to get up and running. Please note you will need to choose one of the Ocelot packages from the NuGet feed. All versions can be found `here `_. diff --git a/docs/introduction/notsupported.rst b/docs/introduction/notsupported.rst index 404b8c70..4d86759a 100644 --- a/docs/introduction/notsupported.rst +++ b/docs/introduction/notsupported.rst @@ -7,10 +7,7 @@ Ocelot does not support... * 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 - Startup.cs or Program.cs. The code sample below registers a piece of middleware that loads your hand rolled swagger.json and returns - it on /swagger/v1/swagger.json. It then registers the SwaggerUI middleware from Swashbuckle.AspNetCore +* 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 Startup.cs or Program.cs. The code sample below registers a piece of middleware that loads your hand rolled swagger.json and returns it on /swagger/v1/swagger.json. It then registers the SwaggerUI middleware from Swashbuckle.AspNetCore .. code-block:: csharp @@ -28,16 +25,8 @@ Ocelot does not support... app.UseOcelot().Wait(); -The main reasons why I don't think Swagger makes sense is we already hand roll our definition in ocelot.json. -If we want people developing against Ocelot to be able to see what routes are available then either share the ocelot.json -with them (This should be as easy as granting access to a repo etc) or use the Ocelot administration API so that they can query Ocelot for the configuration. +The main reasons why I don't think Swagger makes sense is we already hand roll our definition in ocelot.json. If we want people developing against Ocelot to be able to see what routes are available then either share the ocelot.json with them (This should be as easy as granting access to a repo etc) or use the Ocelot administration API so that they can query Ocelot for the configuration. -In addition to this many people will configure Ocelot to proxy all traffic like /products/{everything} to their product service -and you would not be describing what is actually available if you parsed this and turned it into a Swagger path. Also Ocelot has -no concept of the models that the downstream services can return and linking to the above problem the same endpoint can return -multiple models. Ocelot does not know what models might be used in POST, PUT etc so it all gets a bit messy and finally the Swashbuckle -package doesnt reload swagger.json if it changes during runtime. Ocelot's configuration can change during runtime so the Swagger and Ocelot -information would not match. Unless I rolled my own Swagger implementation. +In addition to this many people will configure Ocelot to proxy all traffic like /products/{everything} to their product service and you would not be describing what is actually available if you parsed this and turned it into a Swagger path. Also Ocelot has no concept of the models that the downstream services can return and linking to the above problem the same endpoint can return multiple models. Ocelot does not know what models might be used in POST, PUT etc so it all gets a bit messy and finally the Swashbuckle package doesnt reload swagger.json if it changes during runtime. Ocelot's configuration can change during runtime so the Swagger and Ocelot information would not match. Unless I rolled my own Swagger implementation. -If the user wants something to easily test against the Ocelot API then I suggest using Postman as a simple way to do this. It might -even be possible to write something that maps ocelot.json to the postman json spec. However I don't intend to do this. +If the user wants something to easily test against the Ocelot API then I suggest using Postman as a simple way to do this. It might even be possible to write something that maps ocelot.json to the postman json spec. However I don't intend to do this. diff --git a/helpers.txt b/helpers.txt new file mode 100644 index 00000000..520e5c1f --- /dev/null +++ b/helpers.txt @@ -0,0 +1,11 @@ +var downstreamReRoute = RequestScopedDataRepository.Get("DownstreamReRoute"); +// todo check downstreamReRoute is ok + +var errors = RequestScopedDataRepository.Get>("Errors"); +// todo check errors is ok + +var downstreamResponse = RequestScopedDataRepository.Get("DownstreamResponse"); +// todo check downstreamResponse is ok + +var context = RequestScopedDataRepository.Get("DownstreamContext").Data; +// todo check downstreamcontext is ok \ No newline at end of file diff --git a/src/Ocelot.Provider.Consul/UnableToSetConfigInConsulError.cs b/src/Ocelot.Provider.Consul/UnableToSetConfigInConsulError.cs index bc3103ac..a0f0579c 100644 --- a/src/Ocelot.Provider.Consul/UnableToSetConfigInConsulError.cs +++ b/src/Ocelot.Provider.Consul/UnableToSetConfigInConsulError.cs @@ -1,11 +1,11 @@ namespace Ocelot.Provider.Consul { - using Errors; + using Ocelot.Errors; public class UnableToSetConfigInConsulError : Error { public UnableToSetConfigInConsulError(string s) - : base(s, OcelotErrorCode.UnknownError) + : base(s, OcelotErrorCode.UnknownError, 404) { } } diff --git a/src/Ocelot.Provider.Polly/RequestTimedOutError.cs b/src/Ocelot.Provider.Polly/RequestTimedOutError.cs index 63ad74ae..3c52a8dd 100644 --- a/src/Ocelot.Provider.Polly/RequestTimedOutError.cs +++ b/src/Ocelot.Provider.Polly/RequestTimedOutError.cs @@ -1,12 +1,12 @@ namespace Ocelot.Provider.Polly { - using Errors; + using Ocelot.Errors; using System; public class RequestTimedOutError : Error { public RequestTimedOutError(Exception exception) - : base($"Timeout making http request, exception: {exception}", OcelotErrorCode.RequestTimedOutError) + : base($"Timeout making http request, exception: {exception}", OcelotErrorCode.RequestTimedOutError, 503) { } } diff --git a/src/Ocelot.Provider.Rafty/UnableToSaveAcceptCommand.cs b/src/Ocelot.Provider.Rafty/UnableToSaveAcceptCommand.cs index 1a9f12e2..961cd6ed 100644 --- a/src/Ocelot.Provider.Rafty/UnableToSaveAcceptCommand.cs +++ b/src/Ocelot.Provider.Rafty/UnableToSaveAcceptCommand.cs @@ -5,7 +5,7 @@ public class UnableToSaveAcceptCommand : Error { public UnableToSaveAcceptCommand(string message) - : base(message, OcelotErrorCode.UnknownError) + : base(message, OcelotErrorCode.UnknownError, 404) { } } diff --git a/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs index 942e8cac..67a8ff31 100644 --- a/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs +++ b/src/Ocelot/Authentication/Middleware/AuthenticationMiddleware.cs @@ -1,52 +1,56 @@ -using Microsoft.AspNetCore.Authentication; -using Ocelot.Configuration; -using Ocelot.Logging; -using Ocelot.Middleware; -using System.Threading.Tasks; - -namespace Ocelot.Authentication.Middleware +namespace Ocelot.Authentication.Middleware { + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Authentication; + using Ocelot.Configuration; + using Ocelot.Logging; + using Ocelot.Middleware; + using System.Threading.Tasks; + using Ocelot.DownstreamRouteFinder.Middleware; + public class AuthenticationMiddleware : OcelotMiddleware { - private readonly OcelotRequestDelegate _next; + private readonly RequestDelegate _next; - public AuthenticationMiddleware(OcelotRequestDelegate next, + public AuthenticationMiddleware(RequestDelegate next, IOcelotLoggerFactory loggerFactory) : base(loggerFactory.CreateLogger()) { _next = next; } - public async Task Invoke(DownstreamContext context) + public async Task Invoke(HttpContext httpContext) { - if (context.HttpContext.Request.Method.ToUpper() != "OPTIONS" && IsAuthenticatedRoute(context.DownstreamReRoute)) + var downstreamReRoute = httpContext.Items.DownstreamReRoute(); + + if (httpContext.Request.Method.ToUpper() != "OPTIONS" && IsAuthenticatedRoute(downstreamReRoute)) { - Logger.LogInformation($"{context.HttpContext.Request.Path} is an authenticated route. {MiddlewareName} checking if client is authenticated"); + Logger.LogInformation($"{httpContext.Request.Path} is an authenticated route. {MiddlewareName} checking if client is authenticated"); - var result = await context.HttpContext.AuthenticateAsync(context.DownstreamReRoute.AuthenticationOptions.AuthenticationProviderKey); + var result = await httpContext.AuthenticateAsync(downstreamReRoute.AuthenticationOptions.AuthenticationProviderKey); - context.HttpContext.User = result.Principal; + httpContext.User = result.Principal; - if (context.HttpContext.User.Identity.IsAuthenticated) + if (httpContext.User.Identity.IsAuthenticated) { - Logger.LogInformation($"Client has been authenticated for {context.HttpContext.Request.Path}"); - await _next.Invoke(context); + Logger.LogInformation($"Client has been authenticated for {httpContext.Request.Path}"); + await _next.Invoke(httpContext); } else { var error = new UnauthenticatedError( - $"Request for authenticated route {context.HttpContext.Request.Path} by {context.HttpContext.User.Identity.Name} was unauthenticated"); + $"Request for authenticated route {httpContext.Request.Path} by {httpContext.User.Identity.Name} was unauthenticated"); - Logger.LogWarning($"Client has NOT been authenticated for {context.HttpContext.Request.Path} and pipeline error set. {error}"); + Logger.LogWarning($"Client has NOT been authenticated for {httpContext.Request.Path} and pipeline error set. {error}"); - SetPipelineError(context, error); + httpContext.Items.SetError(error); } } else { - Logger.LogInformation($"No authentication needed for {context.HttpContext.Request.Path}"); + Logger.LogInformation($"No authentication needed for {httpContext.Request.Path}"); - await _next.Invoke(context); + await _next.Invoke(httpContext); } } diff --git a/src/Ocelot/Authentication/Middleware/AuthenticationMiddlewareMiddlewareExtensions.cs b/src/Ocelot/Authentication/Middleware/AuthenticationMiddlewareMiddlewareExtensions.cs index edd49f32..3d3029f8 100644 --- a/src/Ocelot/Authentication/Middleware/AuthenticationMiddlewareMiddlewareExtensions.cs +++ b/src/Ocelot/Authentication/Middleware/AuthenticationMiddlewareMiddlewareExtensions.cs @@ -1,12 +1,12 @@ -using Ocelot.Middleware.Pipeline; - -namespace Ocelot.Authentication.Middleware -{ - public static class AuthenticationMiddlewareMiddlewareExtensions - { - public static IOcelotPipelineBuilder UseAuthenticationMiddleware(this IOcelotPipelineBuilder builder) - { - return builder.UseMiddleware(); - } - } +namespace Ocelot.Authentication.Middleware +{ + using Microsoft.AspNetCore.Builder; + + public static class AuthenticationMiddlewareMiddlewareExtensions + { + public static IApplicationBuilder UseAuthenticationMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } } diff --git a/src/Ocelot/Authorisation/ClaimValueNotAuthorisedError.cs b/src/Ocelot/Authorisation/ClaimValueNotAuthorisedError.cs index d0535624..98f8309f 100644 --- a/src/Ocelot/Authorisation/ClaimValueNotAuthorisedError.cs +++ b/src/Ocelot/Authorisation/ClaimValueNotAuthorisedError.cs @@ -1,11 +1,12 @@ -using Ocelot.Errors; +namespace Ocelot.Authorisation +{ + using Ocelot.Errors; + using System.Net; -namespace Ocelot.Authorisation -{ public class ClaimValueNotAuthorisedError : Error { public ClaimValueNotAuthorisedError(string message) - : base(message, OcelotErrorCode.ClaimValueNotAuthorisedError) + : base(message, OcelotErrorCode.ClaimValueNotAuthorisedError, 403) { } } diff --git a/src/Ocelot/Authorisation/ClaimsAuthoriser.cs b/src/Ocelot/Authorisation/ClaimsAuthoriser.cs index 5ae95e91..d6c99440 100644 --- a/src/Ocelot/Authorisation/ClaimsAuthoriser.cs +++ b/src/Ocelot/Authorisation/ClaimsAuthoriser.cs @@ -1,91 +1,90 @@ -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Responses; -using System.Collections.Generic; -using System.Linq; -using System.Security.Claims; -using System.Text.RegularExpressions; - -namespace Ocelot.Authorisation -{ - using Infrastructure.Claims.Parser; - - public class ClaimsAuthoriser : IClaimsAuthoriser - { - private readonly IClaimsParser _claimsParser; - - public ClaimsAuthoriser(IClaimsParser claimsParser) - { - _claimsParser = claimsParser; - } - +namespace Ocelot.Authorisation +{ + using Ocelot.Infrastructure.Claims.Parser; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Responses; + using System.Collections.Generic; + using System.Linq; + using System.Security.Claims; + using System.Text.RegularExpressions; + + public class ClaimsAuthoriser : IClaimsAuthoriser + { + private readonly IClaimsParser _claimsParser; + + public ClaimsAuthoriser(IClaimsParser claimsParser) + { + _claimsParser = claimsParser; + } + public Response Authorise( - ClaimsPrincipal claimsPrincipal, - Dictionary routeClaimsRequirement, - List urlPathPlaceholderNameAndValues + ClaimsPrincipal claimsPrincipal, + Dictionary routeClaimsRequirement, + List urlPathPlaceholderNameAndValues ) - { - foreach (var required in routeClaimsRequirement) - { - var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, required.Key); - - if (values.IsError) - { - return new ErrorResponse(values.Errors); - } - - if (values.Data != null) + { + foreach (var required in routeClaimsRequirement) + { + var values = _claimsParser.GetValuesByClaimType(claimsPrincipal.Claims, required.Key); + + if (values.IsError) { - // dynamic claim - var match = Regex.Match(required.Value, @"^{(?.+)}$"); - if (match.Success) - { - var variableName = match.Captures[0].Value; - - var matchingPlaceholders = urlPathPlaceholderNameAndValues.Where(p => p.Name.Equals(variableName)).Take(2).ToArray(); - if (matchingPlaceholders.Length == 1) - { - // match - var actualValue = matchingPlaceholders[0].Value; - var authorised = values.Data.Contains(actualValue); - if (!authorised) - { - return new ErrorResponse(new ClaimValueNotAuthorisedError( - $"dynamic claim value for {variableName} of {string.Join(", ", values.Data)} is not the same as required value: {actualValue}")); - } - } - else - { - // config error - if (matchingPlaceholders.Length == 0) - { - return new ErrorResponse(new ClaimValueNotAuthorisedError( - $"config error: requires variable claim value: {variableName} placeholders does not contain that variable: {string.Join(", ", urlPathPlaceholderNameAndValues.Select(p => p.Name))}")); - } - else - { - return new ErrorResponse(new ClaimValueNotAuthorisedError( - $"config error: requires variable claim value: {required.Value} but placeholders are ambiguous: {string.Join(", ", urlPathPlaceholderNameAndValues.Where(p => p.Name.Equals(variableName)).Select(p => p.Value))}")); - } - } - } - else + return new ErrorResponse(values.Errors); + } + + if (values.Data != null) + { + // dynamic claim + var match = Regex.Match(required.Value, @"^{(?.+)}$"); + if (match.Success) { - // static claim - var authorised = values.Data.Contains(required.Value); - if (!authorised) - { - return new ErrorResponse(new ClaimValueNotAuthorisedError( - $"claim value: {string.Join(", ", values.Data)} is not the same as required value: {required.Value} for type: {required.Key}")); - } - } - } - else - { - return new ErrorResponse(new UserDoesNotHaveClaimError($"user does not have claim {required.Key}")); - } - } - - return new OkResponse(true); - } - } + var variableName = match.Captures[0].Value; + + var matchingPlaceholders = urlPathPlaceholderNameAndValues.Where(p => p.Name.Equals(variableName)).Take(2).ToArray(); + if (matchingPlaceholders.Length == 1) + { + // match + var actualValue = matchingPlaceholders[0].Value; + var authorised = values.Data.Contains(actualValue); + if (!authorised) + { + return new ErrorResponse(new ClaimValueNotAuthorisedError( + $"dynamic claim value for {variableName} of {string.Join(", ", values.Data)} is not the same as required value: {actualValue}")); + } + } + else + { + // config error + if (matchingPlaceholders.Length == 0) + { + return new ErrorResponse(new ClaimValueNotAuthorisedError( + $"config error: requires variable claim value: {variableName} placeholders does not contain that variable: {string.Join(", ", urlPathPlaceholderNameAndValues.Select(p => p.Name))}")); + } + else + { + return new ErrorResponse(new ClaimValueNotAuthorisedError( + $"config error: requires variable claim value: {required.Value} but placeholders are ambiguous: {string.Join(", ", urlPathPlaceholderNameAndValues.Where(p => p.Name.Equals(variableName)).Select(p => p.Value))}")); + } + } + } + else + { + // static claim + var authorised = values.Data.Contains(required.Value); + if (!authorised) + { + return new ErrorResponse(new ClaimValueNotAuthorisedError( + $"claim value: {string.Join(", ", values.Data)} is not the same as required value: {required.Value} for type: {required.Key}")); + } + } + } + else + { + return new ErrorResponse(new UserDoesNotHaveClaimError($"user does not have claim {required.Key}")); + } + } + + return new OkResponse(true); + } + } } diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs index ca2f118f..09b55644 100644 --- a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs +++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddleware.cs @@ -1,108 +1,112 @@ -namespace Ocelot.Authorisation.Middleware -{ - using Configuration; - using Logging; - using Ocelot.Middleware; - using Responses; - using System.Threading.Tasks; - - public class AuthorisationMiddleware : OcelotMiddleware - { - private readonly OcelotRequestDelegate _next; - private readonly IClaimsAuthoriser _claimsAuthoriser; - private readonly IScopesAuthoriser _scopesAuthoriser; - - public AuthorisationMiddleware(OcelotRequestDelegate next, - IClaimsAuthoriser claimsAuthoriser, - IScopesAuthoriser scopesAuthoriser, - IOcelotLoggerFactory loggerFactory) - : base(loggerFactory.CreateLogger()) - { - _next = next; - _claimsAuthoriser = claimsAuthoriser; - _scopesAuthoriser = scopesAuthoriser; - } - - public async Task Invoke(DownstreamContext context) - { - if (!IsOptionsHttpMethod(context) && IsAuthenticatedRoute(context.DownstreamReRoute)) - { - Logger.LogInformation("route is authenticated scopes must be checked"); - - var authorised = _scopesAuthoriser.Authorise(context.HttpContext.User, context.DownstreamReRoute.AuthenticationOptions.AllowedScopes); - - if (authorised.IsError) - { - Logger.LogWarning("error authorising user scopes"); - - SetPipelineError(context, authorised.Errors); - return; - } - - if (IsAuthorised(authorised)) - { - Logger.LogInformation("user scopes is authorised calling next authorisation checks"); - } - else - { - Logger.LogWarning("user scopes is not authorised setting pipeline error"); - - SetPipelineError(context, new UnauthorisedError( - $"{context.HttpContext.User.Identity.Name} unable to access {context.DownstreamReRoute.UpstreamPathTemplate.OriginalValue}")); - } - } - - if (!IsOptionsHttpMethod(context) && IsAuthorisedRoute(context.DownstreamReRoute)) - { - Logger.LogInformation("route is authorised"); +namespace Ocelot.Authorisation.Middleware +{ + using Ocelot.Configuration; + using Ocelot.Logging; + using Ocelot.Middleware; + using Ocelot.Responses; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Ocelot.DownstreamRouteFinder.Middleware; - var authorised = _claimsAuthoriser.Authorise(context.HttpContext.User, context.DownstreamReRoute.RouteClaimsRequirement, context.TemplatePlaceholderNameAndValues); - - if (authorised.IsError) - { - Logger.LogWarning($"Error whilst authorising {context.HttpContext.User.Identity.Name}. Setting pipeline error"); - - SetPipelineError(context, authorised.Errors); - return; - } - - if (IsAuthorised(authorised)) - { - Logger.LogInformation($"{context.HttpContext.User.Identity.Name} has succesfully been authorised for {context.DownstreamReRoute.UpstreamPathTemplate.OriginalValue}."); - await _next.Invoke(context); - } - else - { - Logger.LogWarning($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.OriginalValue}. Setting pipeline error"); - - SetPipelineError(context, new UnauthorisedError($"{context.HttpContext.User.Identity.Name} is not authorised to access {context.DownstreamReRoute.UpstreamPathTemplate.OriginalValue}")); - } - } - else - { - Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} route does not require user to be authorised"); - await _next.Invoke(context); - } - } - - private static bool IsAuthorised(Response authorised) - { - return authorised.Data; - } - - private static bool IsAuthenticatedRoute(DownstreamReRoute reRoute) - { - return reRoute.IsAuthenticated; - } - - private static bool IsAuthorisedRoute(DownstreamReRoute reRoute) - { - return reRoute.IsAuthorised; + public class AuthorisationMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IClaimsAuthoriser _claimsAuthoriser; + private readonly IScopesAuthoriser _scopesAuthoriser; + + public AuthorisationMiddleware(RequestDelegate next, + IClaimsAuthoriser claimsAuthoriser, + IScopesAuthoriser scopesAuthoriser, + IOcelotLoggerFactory loggerFactory) + : base(loggerFactory.CreateLogger()) + { + _next = next; + _claimsAuthoriser = claimsAuthoriser; + _scopesAuthoriser = scopesAuthoriser; } - private static bool IsOptionsHttpMethod(DownstreamContext context) + public async Task Invoke(HttpContext httpContext) { - return context.HttpContext.Request.Method.ToUpper() == "OPTIONS"; - } - } + var downstreamReRoute = httpContext.Items.DownstreamReRoute(); + + if (!IsOptionsHttpMethod(httpContext) && IsAuthenticatedRoute(downstreamReRoute)) + { + Logger.LogInformation("route is authenticated scopes must be checked"); + + var authorised = _scopesAuthoriser.Authorise(httpContext.User, downstreamReRoute.AuthenticationOptions.AllowedScopes); + + if (authorised.IsError) + { + Logger.LogWarning("error authorising user scopes"); + + httpContext.Items.UpsertErrors(authorised.Errors); + return; + } + + if (IsAuthorised(authorised)) + { + Logger.LogInformation("user scopes is authorised calling next authorisation checks"); + } + else + { + Logger.LogWarning("user scopes is not authorised setting pipeline error"); + + httpContext.Items.SetError(new UnauthorisedError( + $"{httpContext.User.Identity.Name} unable to access {downstreamReRoute.UpstreamPathTemplate.OriginalValue}")); + } + } + + if (!IsOptionsHttpMethod(httpContext) && IsAuthorisedRoute(downstreamReRoute)) + { + Logger.LogInformation("route is authorised"); + + var authorised = _claimsAuthoriser.Authorise(httpContext.User, downstreamReRoute.RouteClaimsRequirement, httpContext.Items.TemplatePlaceholderNameAndValues()); + + if (authorised.IsError) + { + Logger.LogWarning($"Error whilst authorising {httpContext.User.Identity.Name}. Setting pipeline error"); + + httpContext.Items.UpsertErrors(authorised.Errors); + return; + } + + if (IsAuthorised(authorised)) + { + Logger.LogInformation($"{httpContext.User.Identity.Name} has succesfully been authorised for {downstreamReRoute.UpstreamPathTemplate.OriginalValue}."); + await _next.Invoke(httpContext); + } + else + { + Logger.LogWarning($"{httpContext.User.Identity.Name} is not authorised to access {downstreamReRoute.UpstreamPathTemplate.OriginalValue}. Setting pipeline error"); + + httpContext.Items.SetError(new UnauthorisedError($"{httpContext.User.Identity.Name} is not authorised to access {downstreamReRoute.UpstreamPathTemplate.OriginalValue}")); + } + } + else + { + Logger.LogInformation($"{downstreamReRoute.DownstreamPathTemplate.Value} route does not require user to be authorised"); + await _next.Invoke(httpContext); + } + } + + private static bool IsAuthorised(Response authorised) + { + return authorised.Data; + } + + private static bool IsAuthenticatedRoute(DownstreamReRoute reRoute) + { + return reRoute.IsAuthenticated; + } + + private static bool IsAuthorisedRoute(DownstreamReRoute reRoute) + { + return reRoute.IsAuthorised; + } + + private static bool IsOptionsHttpMethod(HttpContext httpContext) + { + return httpContext.Request.Method.ToUpper() == "OPTIONS"; + } + } } diff --git a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs index a4999381..0bd1579a 100644 --- a/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs +++ b/src/Ocelot/Authorisation/Middleware/AuthorisationMiddlewareMiddlewareExtensions.cs @@ -1,12 +1,12 @@ -using Ocelot.Middleware.Pipeline; - -namespace Ocelot.Authorisation.Middleware -{ - public static class AuthorisationMiddlewareMiddlewareExtensions - { - public static IOcelotPipelineBuilder UseAuthorisationMiddleware(this IOcelotPipelineBuilder builder) - { - return builder.UseMiddleware(); - } - } +namespace Ocelot.Authorisation.Middleware +{ + using Microsoft.AspNetCore.Builder; + + public static class AuthorisationMiddlewareMiddlewareExtensions + { + public static IApplicationBuilder UseAuthorisationMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } } diff --git a/src/Ocelot/Authorisation/ScopeNotAuthorisedError.cs b/src/Ocelot/Authorisation/ScopeNotAuthorisedError.cs index 1abf5715..c7f403de 100644 --- a/src/Ocelot/Authorisation/ScopeNotAuthorisedError.cs +++ b/src/Ocelot/Authorisation/ScopeNotAuthorisedError.cs @@ -1,11 +1,11 @@ -using Ocelot.Errors; +namespace Ocelot.Authorisation +{ + using Ocelot.Errors; -namespace Ocelot.Authorisation -{ public class ScopeNotAuthorisedError : Error { public ScopeNotAuthorisedError(string message) - : base(message, OcelotErrorCode.ScopeNotAuthorisedError) + : base(message, OcelotErrorCode.ScopeNotAuthorisedError, 403) { } } diff --git a/src/Ocelot/Authorisation/UnauthorisedError.cs b/src/Ocelot/Authorisation/UnauthorisedError.cs index 5e8f054c..fc32ea83 100644 --- a/src/Ocelot/Authorisation/UnauthorisedError.cs +++ b/src/Ocelot/Authorisation/UnauthorisedError.cs @@ -1,12 +1,12 @@ -using Ocelot.Errors; - -namespace Ocelot.Authorisation -{ - public class UnauthorisedError : Error - { +namespace Ocelot.Authorisation +{ + using Ocelot.Errors; + + public class UnauthorisedError : Error + { public UnauthorisedError(string message) - : base(message, OcelotErrorCode.UnauthorizedError) - { - } - } + : base(message, OcelotErrorCode.UnauthorizedError, 403) + { + } + } } diff --git a/src/Ocelot/Authorisation/UserDoesNotHaveClaimError.cs b/src/Ocelot/Authorisation/UserDoesNotHaveClaimError.cs index 2cf8211d..6f9aa3eb 100644 --- a/src/Ocelot/Authorisation/UserDoesNotHaveClaimError.cs +++ b/src/Ocelot/Authorisation/UserDoesNotHaveClaimError.cs @@ -1,11 +1,11 @@ -using Ocelot.Errors; +namespace Ocelot.Authorisation +{ + using Ocelot.Errors; -namespace Ocelot.Authorisation -{ public class UserDoesNotHaveClaimError : Error { public UserDoesNotHaveClaimError(string message) - : base(message, OcelotErrorCode.UserDoesNotHaveClaimError) + : base(message, OcelotErrorCode.UserDoesNotHaveClaimError, 403) { } } diff --git a/src/Ocelot/Cache/CacheKeyGenerator.cs b/src/Ocelot/Cache/CacheKeyGenerator.cs index 29b2174b..2c1f44d0 100644 --- a/src/Ocelot/Cache/CacheKeyGenerator.cs +++ b/src/Ocelot/Cache/CacheKeyGenerator.cs @@ -1,18 +1,18 @@ -using Ocelot.Middleware; -using System.Text; -using System.Threading.Tasks; - -namespace Ocelot.Cache +namespace Ocelot.Cache { + using Ocelot.Request.Middleware; + using System.Text; + using System.Threading.Tasks; + public class CacheKeyGenerator : ICacheKeyGenerator { - public string GenerateRequestCacheKey(DownstreamContext context) + public string GenerateRequestCacheKey(DownstreamRequest downstreamRequest) { string hashedContent = null; - StringBuilder downStreamUrlKeyBuilder = new StringBuilder($"{context.DownstreamRequest.Method}-{context.DownstreamRequest.OriginalString}"); - if (context.DownstreamRequest.Content != null) + StringBuilder downStreamUrlKeyBuilder = new StringBuilder($"{downstreamRequest.Method}-{downstreamRequest.OriginalString}"); + if (downstreamRequest.Content != null) { - string requestContentString = Task.Run(async () => await context.DownstreamRequest.Content.ReadAsStringAsync()).Result; + string requestContentString = Task.Run(async () => await downstreamRequest.Content.ReadAsStringAsync()).Result; downStreamUrlKeyBuilder.Append(requestContentString); } diff --git a/src/Ocelot/Cache/ICacheKeyGenerator.cs b/src/Ocelot/Cache/ICacheKeyGenerator.cs index fc6ad52b..838b9ecd 100644 --- a/src/Ocelot/Cache/ICacheKeyGenerator.cs +++ b/src/Ocelot/Cache/ICacheKeyGenerator.cs @@ -1,9 +1,9 @@ -using Ocelot.Middleware; - -namespace Ocelot.Cache +namespace Ocelot.Cache { + using Ocelot.Request.Middleware; + public interface ICacheKeyGenerator { - string GenerateRequestCacheKey(DownstreamContext context); + string GenerateRequestCacheKey(DownstreamRequest downstreamRequest); } } diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs index 615332eb..407393d9 100644 --- a/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddleware.cs @@ -1,122 +1,129 @@ -namespace Ocelot.Cache.Middleware -{ - using Ocelot.Logging; - using Ocelot.Middleware; - using System; - using System.IO; - using System.Linq; - using System.Net.Http; - using System.Text; - using System.Threading.Tasks; +namespace Ocelot.Cache.Middleware +{ + using Ocelot.Logging; + using Ocelot.Middleware; + using System; + using System.IO; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Ocelot.DownstreamRouteFinder.Middleware; - public class OutputCacheMiddleware : OcelotMiddleware - { - private readonly OcelotRequestDelegate _next; - private readonly IOcelotCache _outputCache; - private readonly ICacheKeyGenerator _cacheGeneratot; - - public OutputCacheMiddleware(OcelotRequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IOcelotCache outputCache, - ICacheKeyGenerator cacheGeneratot) - : base(loggerFactory.CreateLogger()) - { - _next = next; - _outputCache = outputCache; - _cacheGeneratot = cacheGeneratot; - } - - public async Task Invoke(DownstreamContext context) - { - if (!context.DownstreamReRoute.IsCached) - { - await _next.Invoke(context); - return; + public class OutputCacheMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IOcelotCache _outputCache; + private readonly ICacheKeyGenerator _cacheGenerator; + + public OutputCacheMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IOcelotCache outputCache, + ICacheKeyGenerator cacheGenerator) + : base(loggerFactory.CreateLogger()) + { + _next = next; + _outputCache = outputCache; + _cacheGenerator = cacheGenerator; + } + + public async Task Invoke(HttpContext httpContext) + { + var downstreamReRoute = httpContext.Items.DownstreamReRoute(); + + if (!downstreamReRoute.IsCached) + { + await _next.Invoke(httpContext); + return; } - var downstreamUrlKey = $"{context.DownstreamRequest.Method}-{context.DownstreamRequest.OriginalString}"; - string downStreamRequestCacheKey = _cacheGeneratot.GenerateRequestCacheKey(context); - - Logger.LogDebug($"Started checking cache for {downstreamUrlKey}"); - - var cached = _outputCache.Get(downStreamRequestCacheKey, context.DownstreamReRoute.CacheOptions.Region); - - if (cached != null) - { - Logger.LogDebug($"cache entry exists for {downstreamUrlKey}"); - - var response = CreateHttpResponseMessage(cached); - SetHttpResponseMessageThisRequest(context, response); - - Logger.LogDebug($"finished returned cached response for {downstreamUrlKey}"); - - return; + var downstreamRequest = httpContext.Items.DownstreamRequest(); + + var downstreamUrlKey = $"{downstreamRequest.Method}-{downstreamRequest.OriginalString}"; + string downStreamRequestCacheKey = _cacheGenerator.GenerateRequestCacheKey(downstreamRequest); + + Logger.LogDebug($"Started checking cache for {downstreamUrlKey}"); + + var cached = _outputCache.Get(downStreamRequestCacheKey, downstreamReRoute.CacheOptions.Region); + + if (cached != null) + { + Logger.LogDebug($"cache entry exists for {downstreamUrlKey}"); + + var response = CreateHttpResponseMessage(cached); + SetHttpResponseMessageThisRequest(httpContext, response); + + Logger.LogDebug($"finished returned cached response for {downstreamUrlKey}"); + + return; + } + + Logger.LogDebug($"no resonse cached for {downstreamUrlKey}"); + + await _next.Invoke(httpContext); + + if (httpContext.Items.Errors().Count > 0) + { + Logger.LogDebug($"there was a pipeline error for {downstreamUrlKey}"); + + return; } - Logger.LogDebug($"no resonse cached for {downstreamUrlKey}"); - - await _next.Invoke(context); - - if (context.IsError) - { - Logger.LogDebug($"there was a pipeline error for {downstreamUrlKey}"); - - return; - } - - cached = await CreateCachedResponse(context.DownstreamResponse); - - _outputCache.Add(downStreamRequestCacheKey, cached, TimeSpan.FromSeconds(context.DownstreamReRoute.CacheOptions.TtlSeconds), context.DownstreamReRoute.CacheOptions.Region); - - Logger.LogDebug($"finished response added to cache for {downstreamUrlKey}"); - } - - private void SetHttpResponseMessageThisRequest(DownstreamContext context, - DownstreamResponse response) - { - context.DownstreamResponse = response; - } - - internal DownstreamResponse CreateHttpResponseMessage(CachedResponse cached) - { - if (cached == null) - { - return null; - } - - var content = new MemoryStream(Convert.FromBase64String(cached.Body)); - - var streamContent = new StreamContent(content); - - foreach (var header in cached.ContentHeaders) - { - streamContent.Headers.TryAddWithoutValidation(header.Key, header.Value); - } - - return new DownstreamResponse(streamContent, cached.StatusCode, cached.Headers.ToList(), cached.ReasonPhrase); - } - - internal async Task CreateCachedResponse(DownstreamResponse response) - { - if (response == null) - { - return null; - } - - var statusCode = response.StatusCode; - var headers = response.Headers.ToDictionary(v => v.Key, v => v.Values); - string body = null; - - if (response.Content != null) - { - var content = await response.Content.ReadAsByteArrayAsync(); - body = Convert.ToBase64String(content); - } - - var contentHeaders = response?.Content?.Headers.ToDictionary(v => v.Key, v => v.Value); - - var cached = new CachedResponse(statusCode, headers, body, contentHeaders, response.ReasonPhrase); - return cached; - } - } -} + var downstreamResponse = httpContext.Items.DownstreamResponse(); + + cached = await CreateCachedResponse(downstreamResponse); + + _outputCache.Add(downStreamRequestCacheKey, cached, TimeSpan.FromSeconds(downstreamReRoute.CacheOptions.TtlSeconds), downstreamReRoute.CacheOptions.Region); + + Logger.LogDebug($"finished response added to cache for {downstreamUrlKey}"); + } + + private void SetHttpResponseMessageThisRequest(HttpContext context, + DownstreamResponse response) + { + context.Items.UpsertDownstreamResponse(response); + } + + internal DownstreamResponse CreateHttpResponseMessage(CachedResponse cached) + { + if (cached == null) + { + return null; + } + + var content = new MemoryStream(Convert.FromBase64String(cached.Body)); + + var streamContent = new StreamContent(content); + + foreach (var header in cached.ContentHeaders) + { + streamContent.Headers.TryAddWithoutValidation(header.Key, header.Value); + } + + return new DownstreamResponse(streamContent, cached.StatusCode, cached.Headers.ToList(), cached.ReasonPhrase); + } + + internal async Task CreateCachedResponse(DownstreamResponse response) + { + if (response == null) + { + return null; + } + + var statusCode = response.StatusCode; + var headers = response.Headers.ToDictionary(v => v.Key, v => v.Values); + string body = null; + + if (response.Content != null) + { + var content = await response.Content.ReadAsByteArrayAsync(); + body = Convert.ToBase64String(content); + } + + var contentHeaders = response?.Content?.Headers.ToDictionary(v => v.Key, v => v.Value); + + var cached = new CachedResponse(statusCode, headers, body, contentHeaders, response.ReasonPhrase); + return cached; + } + } +} diff --git a/src/Ocelot/Cache/Middleware/OutputCacheMiddlewareExtensions.cs b/src/Ocelot/Cache/Middleware/OutputCacheMiddlewareExtensions.cs index fa80a237..48e03290 100644 --- a/src/Ocelot/Cache/Middleware/OutputCacheMiddlewareExtensions.cs +++ b/src/Ocelot/Cache/Middleware/OutputCacheMiddlewareExtensions.cs @@ -1,13 +1,12 @@ -using Microsoft.AspNetCore.Builder; -using Ocelot.Middleware.Pipeline; - -namespace Ocelot.Cache.Middleware -{ - public static class OutputCacheMiddlewareExtensions - { - public static IOcelotPipelineBuilder UseOutputCacheMiddleware(this IOcelotPipelineBuilder builder) - { - return builder.UseMiddleware(); - } - } +namespace Ocelot.Cache.Middleware +{ + using Microsoft.AspNetCore.Builder; + + public static class OutputCacheMiddlewareExtensions + { + public static IApplicationBuilder UseOutputCacheMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } } diff --git a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddlewareExtensions.cs b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddlewareExtensions.cs index 2751d23a..d2d03dd7 100644 --- a/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddlewareExtensions.cs +++ b/src/Ocelot/Claims/Middleware/ClaimsBuilderMiddlewareExtensions.cs @@ -1,10 +1,10 @@ -using Ocelot.Middleware.Pipeline; - -namespace Ocelot.Claims.Middleware +namespace Ocelot.Claims.Middleware { + using Microsoft.AspNetCore.Builder; + public static class ClaimsBuilderMiddlewareExtensions { - public static IOcelotPipelineBuilder UseClaimsToClaimsMiddleware(this IOcelotPipelineBuilder builder) + public static IApplicationBuilder UseClaimsToClaimsMiddleware(this IApplicationBuilder builder) { return builder.UseMiddleware(); } diff --git a/src/Ocelot/Claims/Middleware/ClaimsToClaimsMiddleware.cs b/src/Ocelot/Claims/Middleware/ClaimsToClaimsMiddleware.cs index 58838a97..4b7b73fb 100644 --- a/src/Ocelot/Claims/Middleware/ClaimsToClaimsMiddleware.cs +++ b/src/Ocelot/Claims/Middleware/ClaimsToClaimsMiddleware.cs @@ -1,42 +1,46 @@ -using Ocelot.Logging; -using Ocelot.Middleware; -using System.Linq; -using System.Threading.Tasks; - -namespace Ocelot.Claims.Middleware -{ - public class ClaimsToClaimsMiddleware : OcelotMiddleware - { - private readonly OcelotRequestDelegate _next; - private readonly IAddClaimsToRequest _addClaimsToRequest; - - public ClaimsToClaimsMiddleware(OcelotRequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IAddClaimsToRequest addClaimsToRequest) - : base(loggerFactory.CreateLogger()) - { - _next = next; - _addClaimsToRequest = addClaimsToRequest; - } - - public async Task Invoke(DownstreamContext context) - { - if (context.DownstreamReRoute.ClaimsToClaims.Any()) - { - Logger.LogDebug("this route has instructions to convert claims to other claims"); - - var result = _addClaimsToRequest.SetClaimsOnContext(context.DownstreamReRoute.ClaimsToClaims, context.HttpContext); - - if (result.IsError) - { - Logger.LogDebug("error converting claims to other claims, setting pipeline error"); - - SetPipelineError(context, result.Errors); - return; - } - } - - await _next.Invoke(context); - } - } -} +namespace Ocelot.Claims.Middleware +{ + using Microsoft.AspNetCore.Http; + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.Logging; + using Ocelot.Middleware; + using System.Linq; + using System.Threading.Tasks; + + public class ClaimsToClaimsMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IAddClaimsToRequest _addClaimsToRequest; + + public ClaimsToClaimsMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IAddClaimsToRequest addClaimsToRequest) + : base(loggerFactory.CreateLogger()) + { + _next = next; + _addClaimsToRequest = addClaimsToRequest; + } + + public async Task Invoke(HttpContext httpContext) + { + var downstreamReRoute = httpContext.Items.DownstreamReRoute(); + + if (downstreamReRoute.ClaimsToClaims.Any()) + { + Logger.LogDebug("this route has instructions to convert claims to other claims"); + + var result = _addClaimsToRequest.SetClaimsOnContext(downstreamReRoute.ClaimsToClaims, httpContext); + + if (result.IsError) + { + Logger.LogDebug("error converting claims to other claims, setting pipeline error"); + + httpContext.Items.UpsertErrors(result.Errors); + return; + } + } + + await _next.Invoke(httpContext); + } + } +} diff --git a/src/Ocelot/Configuration/Parser/InstructionNotForClaimsError.cs b/src/Ocelot/Configuration/Parser/InstructionNotForClaimsError.cs index 24535653..b42f4621 100644 --- a/src/Ocelot/Configuration/Parser/InstructionNotForClaimsError.cs +++ b/src/Ocelot/Configuration/Parser/InstructionNotForClaimsError.cs @@ -1,12 +1,12 @@ -using Ocelot.Errors; - -namespace Ocelot.Configuration.Parser -{ - public class InstructionNotForClaimsError : Error - { +using Ocelot.Errors; + +namespace Ocelot.Configuration.Parser +{ + public class InstructionNotForClaimsError : Error + { public InstructionNotForClaimsError() - : base("instructions did not contain claims, at the moment we only support claims extraction", OcelotErrorCode.InstructionNotForClaimsError) - { - } - } + : base("instructions did not contain claims, at the moment we only support claims extraction", OcelotErrorCode.InstructionNotForClaimsError, 404) + { + } + } } diff --git a/src/Ocelot/Configuration/Parser/NoInstructionsError.cs b/src/Ocelot/Configuration/Parser/NoInstructionsError.cs index 67bcd3e1..ff3a0844 100644 --- a/src/Ocelot/Configuration/Parser/NoInstructionsError.cs +++ b/src/Ocelot/Configuration/Parser/NoInstructionsError.cs @@ -1,12 +1,12 @@ -using Ocelot.Errors; - -namespace Ocelot.Configuration.Parser -{ - public class NoInstructionsError : Error - { +using Ocelot.Errors; + +namespace Ocelot.Configuration.Parser +{ + public class NoInstructionsError : Error + { public NoInstructionsError(string splitToken) - : base($"There we no instructions splitting on {splitToken}", OcelotErrorCode.NoInstructionsError) - { - } - } + : base($"There we no instructions splitting on {splitToken}", OcelotErrorCode.NoInstructionsError, 404) + { + } + } } diff --git a/src/Ocelot/Configuration/Parser/ParsingConfigurationHeaderError.cs b/src/Ocelot/Configuration/Parser/ParsingConfigurationHeaderError.cs index fba6a6db..dad57310 100644 --- a/src/Ocelot/Configuration/Parser/ParsingConfigurationHeaderError.cs +++ b/src/Ocelot/Configuration/Parser/ParsingConfigurationHeaderError.cs @@ -1,13 +1,13 @@ -using Ocelot.Errors; -using System; - -namespace Ocelot.Configuration.Parser -{ - public class ParsingConfigurationHeaderError : Error - { +using Ocelot.Errors; +using System; + +namespace Ocelot.Configuration.Parser +{ + public class ParsingConfigurationHeaderError : Error + { public ParsingConfigurationHeaderError(Exception exception) - : base($"error parsing configuration eception is {exception.Message}", OcelotErrorCode.ParsingConfigurationHeaderError) - { - } - } + : base($"error parsing configuration eception is {exception.Message}", OcelotErrorCode.ParsingConfigurationHeaderError, 404) + { + } + } } diff --git a/src/Ocelot/Configuration/ReRoute.cs b/src/Ocelot/Configuration/ReRoute.cs index e7efe12b..d09047bd 100644 --- a/src/Ocelot/Configuration/ReRoute.cs +++ b/src/Ocelot/Configuration/ReRoute.cs @@ -1,32 +1,32 @@ -namespace Ocelot.Configuration -{ - using Ocelot.Configuration.File; - using Ocelot.Values; - using System.Collections.Generic; - using System.Net.Http; - - public class ReRoute - { - public ReRoute(List downstreamReRoute, - List downstreamReRouteConfig, +namespace Ocelot.Configuration +{ + using Ocelot.Configuration.File; + using Ocelot.Values; + using System.Collections.Generic; + using System.Net.Http; + + public class ReRoute + { + public ReRoute(List downstreamReRoute, + List downstreamReRouteConfig, List upstreamHttpMethod, UpstreamPathTemplate upstreamTemplatePattern, - string upstreamHost, - string aggregator) - { - UpstreamHost = upstreamHost; - DownstreamReRoute = downstreamReRoute; - DownstreamReRouteConfig = downstreamReRouteConfig; - UpstreamHttpMethod = upstreamHttpMethod; - UpstreamTemplatePattern = upstreamTemplatePattern; - Aggregator = aggregator; - } - - public UpstreamPathTemplate UpstreamTemplatePattern { get; private set; } - public List UpstreamHttpMethod { get; private set; } - public string UpstreamHost { get; private set; } - public List DownstreamReRoute { get; private set; } - public List DownstreamReRouteConfig { get; private set; } - public string Aggregator { get; private set; } - } + string upstreamHost, + string aggregator) + { + UpstreamHost = upstreamHost; + DownstreamReRoute = downstreamReRoute; + DownstreamReRouteConfig = downstreamReRouteConfig; + UpstreamHttpMethod = upstreamHttpMethod; + UpstreamTemplatePattern = upstreamTemplatePattern; + Aggregator = aggregator; + } + + public UpstreamPathTemplate UpstreamTemplatePattern { get; private set; } + public List UpstreamHttpMethod { get; private set; } + public string UpstreamHost { get; private set; } + public List DownstreamReRoute { get; private set; } + public List DownstreamReRouteConfig { get; private set; } + public string Aggregator { get; private set; } + } } diff --git a/src/Ocelot/Configuration/Validator/FileValidationFailedError.cs b/src/Ocelot/Configuration/Validator/FileValidationFailedError.cs index a1918341..3a55b14b 100644 --- a/src/Ocelot/Configuration/Validator/FileValidationFailedError.cs +++ b/src/Ocelot/Configuration/Validator/FileValidationFailedError.cs @@ -1,12 +1,12 @@ -namespace Ocelot.Configuration.Validator -{ - using Errors; - - public class FileValidationFailedError : Error - { +namespace Ocelot.Configuration.Validator +{ + using Errors; + + public class FileValidationFailedError : Error + { public FileValidationFailedError(string message) - : base(message, OcelotErrorCode.FileValidationFailedError) - { - } - } + : base(message, OcelotErrorCode.FileValidationFailedError, 404) + { + } + } } diff --git a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs index 33b3424e..17c95f1c 100644 --- a/src/Ocelot/DependencyInjection/IOcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/IOcelotBuilder.cs @@ -1,6 +1,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Ocelot.Middleware.Multiplexer; +using Ocelot.Multiplexer; using System; using System.Net.Http; using Ocelot.Configuration; diff --git a/src/Ocelot/DependencyInjection/OcelotBuilder.cs b/src/Ocelot/DependencyInjection/OcelotBuilder.cs index 131fff8f..75686879 100644 --- a/src/Ocelot/DependencyInjection/OcelotBuilder.cs +++ b/src/Ocelot/DependencyInjection/OcelotBuilder.cs @@ -27,7 +27,7 @@ namespace Ocelot.DependencyInjection using Ocelot.LoadBalancer.LoadBalancers; using Ocelot.Logging; using Ocelot.Middleware; - using Ocelot.Middleware.Multiplexer; + using Ocelot.Multiplexer; using Ocelot.PathManipulation; using Ocelot.QueryStrings; using Ocelot.RateLimit; @@ -128,7 +128,6 @@ namespace Ocelot.DependencyInjection Services.TryAddSingleton(); Services.AddMemoryCache(); Services.TryAddSingleton(); - Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); Services.TryAddSingleton(); diff --git a/src/Ocelot/DownstreamPathManipulation/Middleware/ClaimsToDownstreamPathMiddleware.cs b/src/Ocelot/DownstreamPathManipulation/Middleware/ClaimsToDownstreamPathMiddleware.cs index 47107c67..6e696319 100644 --- a/src/Ocelot/DownstreamPathManipulation/Middleware/ClaimsToDownstreamPathMiddleware.cs +++ b/src/Ocelot/DownstreamPathManipulation/Middleware/ClaimsToDownstreamPathMiddleware.cs @@ -1,42 +1,50 @@ -using Ocelot.Logging; -using Ocelot.Middleware; -using System.Linq; -using System.Threading.Tasks; +namespace Ocelot.DownstreamPathManipulation.Middleware +{ + using System.Linq; + using System.Threading.Tasks; + using Ocelot.Logging; + using Microsoft.AspNetCore.Http; + using Ocelot.Middleware; + using Ocelot.PathManipulation; + using Ocelot.DownstreamRouteFinder.Middleware; -namespace Ocelot.PathManipulation.Middleware -{ - public class ClaimsToDownstreamPathMiddleware : OcelotMiddleware - { - private readonly OcelotRequestDelegate _next; - private readonly IChangeDownstreamPathTemplate _changeDownstreamPathTemplate; + public class ClaimsToDownstreamPathMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IChangeDownstreamPathTemplate _changeDownstreamPathTemplate; + + public ClaimsToDownstreamPathMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IChangeDownstreamPathTemplate changeDownstreamPathTemplate) + : base(loggerFactory.CreateLogger()) + { + _next = next; + _changeDownstreamPathTemplate = changeDownstreamPathTemplate; + } + + public async Task Invoke(HttpContext httpContext) + { + var downstreamReRoute = httpContext.Items.DownstreamReRoute(); + + if (downstreamReRoute.ClaimsToPath.Any()) + { + Logger.LogInformation($"{downstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to path"); - public ClaimsToDownstreamPathMiddleware(OcelotRequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IChangeDownstreamPathTemplate changeDownstreamPathTemplate) - : base(loggerFactory.CreateLogger()) - { - _next = next; - _changeDownstreamPathTemplate = changeDownstreamPathTemplate; - } + var templatePlaceholderNameAndValues = httpContext.Items.TemplatePlaceholderNameAndValues(); - public async Task Invoke(DownstreamContext context) - { - if (context.DownstreamReRoute.ClaimsToPath.Any()) - { - Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to path"); - var response = _changeDownstreamPathTemplate.ChangeDownstreamPath(context.DownstreamReRoute.ClaimsToPath, context.HttpContext.User.Claims, - context.DownstreamReRoute.DownstreamPathTemplate, context.TemplatePlaceholderNameAndValues); - - if (response.IsError) - { - Logger.LogWarning("there was an error setting queries on context, setting pipeline error"); - - SetPipelineError(context, response.Errors); - return; - } - } - - await _next.Invoke(context); - } - } -} + var response = _changeDownstreamPathTemplate.ChangeDownstreamPath(downstreamReRoute.ClaimsToPath, httpContext.User.Claims, + downstreamReRoute.DownstreamPathTemplate, templatePlaceholderNameAndValues); + + if (response.IsError) + { + Logger.LogWarning("there was an error setting queries on context, setting pipeline error"); + + httpContext.Items.UpsertErrors(response.Errors); + return; + } + } + + await _next.Invoke(httpContext); + } + } +} diff --git a/src/Ocelot/DownstreamPathManipulation/Middleware/ClaimsToDownstreamPathMiddlewareExtensions.cs b/src/Ocelot/DownstreamPathManipulation/Middleware/ClaimsToDownstreamPathMiddlewareExtensions.cs index 04fbc78d..96a7ed44 100644 --- a/src/Ocelot/DownstreamPathManipulation/Middleware/ClaimsToDownstreamPathMiddlewareExtensions.cs +++ b/src/Ocelot/DownstreamPathManipulation/Middleware/ClaimsToDownstreamPathMiddlewareExtensions.cs @@ -1,10 +1,10 @@ -using Ocelot.Middleware.Pipeline; - -namespace Ocelot.PathManipulation.Middleware +namespace Ocelot.DownstreamPathManipulation.Middleware { + using Microsoft.AspNetCore.Builder; + public static class ClaimsToDownstreamPathMiddlewareExtensions { - public static IOcelotPipelineBuilder UseClaimsToDownstreamPathMiddleware(this IOcelotPipelineBuilder builder) + public static IApplicationBuilder UseClaimsToDownstreamPathMiddleware(this IApplicationBuilder builder) { return builder.UseMiddleware(); } diff --git a/src/Ocelot/DownstreamRouteFinder/DownstreamRoute.cs b/src/Ocelot/DownstreamRouteFinder/DownstreamRoute.cs index a691e11e..2e213336 100644 --- a/src/Ocelot/DownstreamRouteFinder/DownstreamRoute.cs +++ b/src/Ocelot/DownstreamRouteFinder/DownstreamRoute.cs @@ -1,18 +1,22 @@ -using Ocelot.Configuration; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using System.Collections.Generic; - -namespace Ocelot.DownstreamRouteFinder -{ - public class DownstreamRoute - { - public DownstreamRoute(List templatePlaceholderNameAndValues, ReRoute reRoute) - { - TemplatePlaceholderNameAndValues = templatePlaceholderNameAndValues; - ReRoute = reRoute; - } - - public List TemplatePlaceholderNameAndValues { get; private set; } - public ReRoute ReRoute { get; private set; } - } +namespace Ocelot.DownstreamRouteFinder +{ + using Ocelot.Configuration; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using System.Collections.Generic; + + public class DownstreamRoute + { + public DownstreamRoute() + { + } + + public DownstreamRoute(List templatePlaceholderNameAndValues, ReRoute reRoute) + { + TemplatePlaceholderNameAndValues = templatePlaceholderNameAndValues; + ReRoute = reRoute; + } + + public List TemplatePlaceholderNameAndValues { get; private set; } + public ReRoute ReRoute { get; private set; } + } } diff --git a/src/Ocelot/DownstreamRouteFinder/Finder/UnableToFindDownstreamRouteError.cs b/src/Ocelot/DownstreamRouteFinder/Finder/UnableToFindDownstreamRouteError.cs index 5a0a358c..ad1b9c84 100644 --- a/src/Ocelot/DownstreamRouteFinder/Finder/UnableToFindDownstreamRouteError.cs +++ b/src/Ocelot/DownstreamRouteFinder/Finder/UnableToFindDownstreamRouteError.cs @@ -1,12 +1,12 @@ -using Ocelot.Errors; - -namespace Ocelot.DownstreamRouteFinder.Finder -{ - public class UnableToFindDownstreamRouteError : Error - { +using Ocelot.Errors; + +namespace Ocelot.DownstreamRouteFinder.Finder +{ + public class UnableToFindDownstreamRouteError : Error + { public UnableToFindDownstreamRouteError(string path, string httpVerb) - : base($"Failed to match ReRoute configuration for upstream path: {path}, verb: {httpVerb}.", OcelotErrorCode.UnableToFindDownstreamRouteError) - { - } - } + : base($"Failed to match ReRoute configuration for upstream path: {path}, verb: {httpVerb}.", OcelotErrorCode.UnableToFindDownstreamRouteError, 404) + { + } + } } diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs index 8ac0705c..995ca535 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddleware.cs @@ -1,59 +1,61 @@ -using Ocelot.DownstreamRouteFinder.Finder; -using Ocelot.Infrastructure.Extensions; -using Ocelot.Logging; -using Ocelot.Middleware; -using Ocelot.Middleware.Multiplexer; -using System.Linq; -using System.Threading.Tasks; - namespace Ocelot.DownstreamRouteFinder.Middleware { + using Microsoft.AspNetCore.Http; + using Ocelot.DownstreamRouteFinder.Finder; + using Ocelot.Infrastructure.Extensions; + using Ocelot.Logging; + using Ocelot.Middleware; + using System.Linq; + using System.Threading.Tasks; + public class DownstreamRouteFinderMiddleware : OcelotMiddleware { - private readonly OcelotRequestDelegate _next; + private readonly RequestDelegate _next; private readonly IDownstreamRouteProviderFactory _factory; - private readonly IMultiplexer _multiplexer; - public DownstreamRouteFinderMiddleware(OcelotRequestDelegate next, + public DownstreamRouteFinderMiddleware(RequestDelegate next, IOcelotLoggerFactory loggerFactory, - IDownstreamRouteProviderFactory downstreamRouteFinder, - IMultiplexer multiplexer) + IDownstreamRouteProviderFactory downstreamRouteFinder + ) : base(loggerFactory.CreateLogger()) { - _multiplexer = multiplexer; _next = next; _factory = downstreamRouteFinder; } - public async Task Invoke(DownstreamContext context) + public async Task Invoke(HttpContext httpContext) { - var upstreamUrlPath = context.HttpContext.Request.Path.ToString(); + var upstreamUrlPath = httpContext.Request.Path.ToString(); - var upstreamQueryString = context.HttpContext.Request.QueryString.ToString(); + var upstreamQueryString = httpContext.Request.QueryString.ToString(); - var upstreamHost = context.HttpContext.Request.Headers["Host"]; + var upstreamHost = httpContext.Request.Headers["Host"]; Logger.LogDebug($"Upstream url path is {upstreamUrlPath}"); - var provider = _factory.Get(context.Configuration); + var internalConfiguration = httpContext.Items.IInternalConfiguration(); - var downstreamRoute = provider.Get(upstreamUrlPath, upstreamQueryString, context.HttpContext.Request.Method, context.Configuration, upstreamHost); + var provider = _factory.Get(internalConfiguration); - if (downstreamRoute.IsError) + var response = provider.Get(upstreamUrlPath, upstreamQueryString, httpContext.Request.Method, internalConfiguration, upstreamHost); + + if (response.IsError) { - Logger.LogWarning($"{MiddlewareName} setting pipeline errors. IDownstreamRouteFinder returned {downstreamRoute.Errors.ToErrorString()}"); + Logger.LogWarning($"{MiddlewareName} setting pipeline errors. IDownstreamRouteFinder returned {response.Errors.ToErrorString()}"); - SetPipelineError(context, downstreamRoute.Errors); + httpContext.Items.UpsertErrors(response.Errors); return; } - var downstreamPathTemplates = string.Join(", ", downstreamRoute.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value)); - + var downstreamPathTemplates = string.Join(", ", response.Data.ReRoute.DownstreamReRoute.Select(r => r.DownstreamPathTemplate.Value)); Logger.LogDebug($"downstream templates are {downstreamPathTemplates}"); - context.TemplatePlaceholderNameAndValues = downstreamRoute.Data.TemplatePlaceholderNameAndValues; + // why set both of these on HttpContext + httpContext.Items.UpsertTemplatePlaceholderNameAndValues(response.Data.TemplatePlaceholderNameAndValues); - await _multiplexer.Multiplex(context, downstreamRoute.Data.ReRoute, _next); + httpContext.Items.UpsertDownstreamRoute(response.Data); + + await _next.Invoke(httpContext); } } } diff --git a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddlewareExtensions.cs b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddlewareExtensions.cs index 08a856b6..11dedad0 100644 --- a/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddlewareExtensions.cs +++ b/src/Ocelot/DownstreamRouteFinder/Middleware/DownstreamRouteFinderMiddlewareExtensions.cs @@ -1,11 +1,10 @@ -using Microsoft.AspNetCore.Builder; -using Ocelot.Middleware.Pipeline; - namespace Ocelot.DownstreamRouteFinder.Middleware { + using Microsoft.AspNetCore.Builder; + public static class DownstreamRouteFinderMiddlewareExtensions { - public static IOcelotPipelineBuilder UseDownstreamRouteFinderMiddleware(this IOcelotPipelineBuilder builder) + public static IApplicationBuilder UseDownstreamRouteFinderMiddleware(this IApplicationBuilder builder) { return builder.UseMiddleware(); } diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs index 7d19d523..992a498f 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddleware.cs @@ -1,132 +1,149 @@ -using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; -using Ocelot.Logging; -using Ocelot.Middleware; -using Ocelot.Responses; -using Ocelot.Values; -using System; -using System.Threading.Tasks; +namespace Ocelot.DownstreamUrlCreator.Middleware +{ + using System.Collections.Generic; + using System.Text.RegularExpressions; + using Ocelot.Configuration; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Microsoft.AspNetCore.Http; + using Ocelot.Request.Middleware; + using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; + using Ocelot.Logging; + using Ocelot.Middleware; + using Ocelot.Responses; + using Ocelot.Values; + using System; + using System.Threading.Tasks; + using Ocelot.DownstreamRouteFinder.Middleware; -namespace Ocelot.DownstreamUrlCreator.Middleware -{ - using System.Text.RegularExpressions; + public class DownstreamUrlCreatorMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IDownstreamPathPlaceholderReplacer _replacer; + + public DownstreamUrlCreatorMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IDownstreamPathPlaceholderReplacer replacer + ) + : base(loggerFactory.CreateLogger()) + { + _next = next; + _replacer = replacer; + } + + public async Task Invoke(HttpContext httpContext) + { + var downstreamReRoute = httpContext.Items.DownstreamReRoute(); - public class DownstreamUrlCreatorMiddleware : OcelotMiddleware - { - private readonly OcelotRequestDelegate _next; - private readonly IDownstreamPathPlaceholderReplacer _replacer; + var templatePlaceholderNameAndValues = httpContext.Items.TemplatePlaceholderNameAndValues(); + + var response = _replacer + .Replace(downstreamReRoute.DownstreamPathTemplate.Value, templatePlaceholderNameAndValues); - public DownstreamUrlCreatorMiddleware(OcelotRequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IDownstreamPathPlaceholderReplacer replacer) - : base(loggerFactory.CreateLogger()) - { - _next = next; - _replacer = replacer; - } + var downstreamRequest = httpContext.Items.DownstreamRequest(); - public async Task Invoke(DownstreamContext context) - { - var response = _replacer - .Replace(context.DownstreamReRoute.DownstreamPathTemplate.Value, context.TemplatePlaceholderNameAndValues); - - if (response.IsError) + if (response.IsError) + { + Logger.LogDebug("IDownstreamPathPlaceholderReplacer returned an error, setting pipeline error"); + + httpContext.Items.UpsertErrors(response.Errors); + return; + } + + if (!string.IsNullOrEmpty(downstreamReRoute.DownstreamScheme)) { - Logger.LogDebug("IDownstreamPathPlaceholderReplacer returned an error, setting pipeline error"); - - SetPipelineError(context, response.Errors); - return; + //todo make sure this works, hopefully there is a test ;E + httpContext.Items.DownstreamRequest().Scheme = downstreamReRoute.DownstreamScheme; } - if (!string.IsNullOrEmpty(context.DownstreamReRoute.DownstreamScheme)) - { - context.DownstreamRequest.Scheme = context.DownstreamReRoute.DownstreamScheme; - } + var internalConfiguration = httpContext.Items.IInternalConfiguration(); - if (ServiceFabricRequest(context)) + if (ServiceFabricRequest(internalConfiguration, downstreamReRoute)) { - var pathAndQuery = CreateServiceFabricUri(context, response); - context.DownstreamRequest.AbsolutePath = pathAndQuery.path; - context.DownstreamRequest.Query = pathAndQuery.query; - } - else - { - var dsPath = response.Data; + var pathAndQuery = CreateServiceFabricUri(downstreamRequest, downstreamReRoute, templatePlaceholderNameAndValues, response); - if (ContainsQueryString(dsPath)) + //todo check this works again hope there is a test.. + downstreamRequest.AbsolutePath = pathAndQuery.path; + downstreamRequest.Query = pathAndQuery.query; + } + else + { + var dsPath = response.Data; + + if (ContainsQueryString(dsPath)) { - context.DownstreamRequest.AbsolutePath = GetPath(dsPath); - - if (string.IsNullOrEmpty(context.DownstreamRequest.Query)) - { - context.DownstreamRequest.Query = GetQueryString(dsPath); - } - else - { - context.DownstreamRequest.Query += GetQueryString(dsPath).Replace('?', '&'); - } - } - else + downstreamRequest.AbsolutePath = GetPath(dsPath); + + if (string.IsNullOrEmpty(downstreamRequest.Query)) + { + downstreamRequest.Query = GetQueryString(dsPath); + } + else + { + downstreamRequest.Query += GetQueryString(dsPath).Replace('?', '&'); + } + } + else { - RemoveQueryStringParametersThatHaveBeenUsedInTemplate(context); - - context.DownstreamRequest.AbsolutePath = dsPath.Value; - } - } - - Logger.LogDebug($"Downstream url is {context.DownstreamRequest}"); - - await _next.Invoke(context); - } - - private static void RemoveQueryStringParametersThatHaveBeenUsedInTemplate(DownstreamContext context) - { - foreach (var nAndV in context.TemplatePlaceholderNameAndValues) - { - var name = nAndV.Name.Replace("{", "").Replace("}", ""); - - if (context.DownstreamRequest.Query.Contains(name) && - context.DownstreamRequest.Query.Contains(nAndV.Value)) - { - var questionMarkOrAmpersand = context.DownstreamRequest.Query.IndexOf(name, StringComparison.Ordinal); - context.DownstreamRequest.Query = context.DownstreamRequest.Query.Remove(questionMarkOrAmpersand - 1, 1); - - var rgx = new Regex($@"\b{name}={nAndV.Value}\b"); - context.DownstreamRequest.Query = rgx.Replace(context.DownstreamRequest.Query, ""); - - if (!string.IsNullOrEmpty(context.DownstreamRequest.Query)) - { - context.DownstreamRequest.Query = '?' + context.DownstreamRequest.Query.Substring(1); - } - } - } - } - - private string GetPath(DownstreamPath dsPath) - { - return dsPath.Value.Substring(0, dsPath.Value.IndexOf("?", StringComparison.Ordinal)); - } - - private string GetQueryString(DownstreamPath dsPath) - { - return dsPath.Value.Substring(dsPath.Value.IndexOf("?", StringComparison.Ordinal)); - } - - private bool ContainsQueryString(DownstreamPath dsPath) - { - return dsPath.Value.Contains("?"); - } - - private (string path, string query) CreateServiceFabricUri(DownstreamContext context, Response dsPath) - { - var query = context.DownstreamRequest.Query; - var serviceName = _replacer.Replace(context.DownstreamReRoute.ServiceName, context.TemplatePlaceholderNameAndValues); - var pathTemplate = $"/{serviceName.Data.Value}{dsPath.Data.Value}"; - return (pathTemplate, query); - } - - private static bool ServiceFabricRequest(DownstreamContext context) - { - return context.Configuration.ServiceProviderConfiguration.Type?.ToLower() == "servicefabric" && context.DownstreamReRoute.UseServiceDiscovery; - } - } -} + RemoveQueryStringParametersThatHaveBeenUsedInTemplate(downstreamRequest, templatePlaceholderNameAndValues); + + downstreamRequest.AbsolutePath = dsPath.Value; + } + } + + Logger.LogDebug($"Downstream url is {downstreamRequest}"); + + await _next.Invoke(httpContext); + } + + private static void RemoveQueryStringParametersThatHaveBeenUsedInTemplate(DownstreamRequest downstreamRequest, List templatePlaceholderNameAndValues) + { + foreach (var nAndV in templatePlaceholderNameAndValues) + { + var name = nAndV.Name.Replace("{", "").Replace("}", ""); + + if (downstreamRequest.Query.Contains(name) && + downstreamRequest.Query.Contains(nAndV.Value)) + { + var questionMarkOrAmpersand = downstreamRequest.Query.IndexOf(name, StringComparison.Ordinal); + downstreamRequest.Query = downstreamRequest.Query.Remove(questionMarkOrAmpersand - 1, 1); + + var rgx = new Regex($@"\b{name}={nAndV.Value}\b"); + downstreamRequest.Query = rgx.Replace(downstreamRequest.Query, ""); + + if (!string.IsNullOrEmpty(downstreamRequest.Query)) + { + downstreamRequest.Query = '?' + downstreamRequest.Query.Substring(1); + } + } + } + } + + private string GetPath(DownstreamPath dsPath) + { + return dsPath.Value.Substring(0, dsPath.Value.IndexOf("?", StringComparison.Ordinal)); + } + + private string GetQueryString(DownstreamPath dsPath) + { + return dsPath.Value.Substring(dsPath.Value.IndexOf("?", StringComparison.Ordinal)); + } + + private bool ContainsQueryString(DownstreamPath dsPath) + { + return dsPath.Value.Contains("?"); + } + + private (string path, string query) CreateServiceFabricUri(DownstreamRequest downstreamRequest, DownstreamReRoute downstreamReRoute, List templatePlaceholderNameAndValues, Response dsPath) + { + var query = downstreamRequest.Query; + var serviceName = _replacer.Replace(downstreamReRoute.ServiceName, templatePlaceholderNameAndValues); + var pathTemplate = $"/{serviceName.Data.Value}{dsPath.Data.Value}"; + return (pathTemplate, query); + } + + private static bool ServiceFabricRequest(IInternalConfiguration config, DownstreamReRoute downstreamReRoute) + { + return config.ServiceProviderConfiguration.Type?.ToLower() == "servicefabric" && downstreamReRoute.UseServiceDiscovery; + } + } +} diff --git a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddlewareExtensions.cs b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddlewareExtensions.cs index 8904d3af..47754cde 100644 --- a/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddlewareExtensions.cs +++ b/src/Ocelot/DownstreamUrlCreator/Middleware/DownstreamUrlCreatorMiddlewareExtensions.cs @@ -1,13 +1,12 @@ -using Microsoft.AspNetCore.Builder; -using Ocelot.Middleware.Pipeline; - -namespace Ocelot.DownstreamUrlCreator.Middleware -{ - public static class DownstreamUrlCreatorMiddlewareExtensions - { - public static IOcelotPipelineBuilder UseDownstreamUrlCreatorMiddleware(this IOcelotPipelineBuilder builder) - { - return builder.UseMiddleware(); - } - } +namespace Ocelot.DownstreamUrlCreator.Middleware +{ + using Microsoft.AspNetCore.Builder; + + public static class DownstreamUrlCreatorMiddlewareExtensions + { + public static IApplicationBuilder UseDownstreamUrlCreatorMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } } diff --git a/src/Ocelot/Errors/Error.cs b/src/Ocelot/Errors/Error.cs index 18c3c49f..cca7b912 100644 --- a/src/Ocelot/Errors/Error.cs +++ b/src/Ocelot/Errors/Error.cs @@ -1,15 +1,19 @@ +using System.Net; + namespace Ocelot.Errors { public abstract class Error { - protected Error(string message, OcelotErrorCode code) - { + protected Error(string message, OcelotErrorCode code, int httpStatusCode) + { + HttpStatusCode = httpStatusCode; Message = message; Code = code; } public string Message { get; private set; } - public OcelotErrorCode Code { get; private set; } + public OcelotErrorCode Code { get; private set; } + public int HttpStatusCode { get; private set; } public override string ToString() { diff --git a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs index 0f6dce42..fef5a969 100644 --- a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs +++ b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddleware.cs @@ -1,102 +1,89 @@ namespace Ocelot.Errors.Middleware { - using Configuration; - using Ocelot.Configuration.Repository; - using Ocelot.Infrastructure.Extensions; + using Ocelot.Configuration; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; using Ocelot.Middleware; using System; using System.Linq; using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Ocelot.DownstreamRouteFinder.Middleware; /// - /// Catches all unhandled exceptions thrown by middleware, logs and returns a 500 + /// 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 RequestDelegate _next; private readonly IRequestScopedDataRepository _repo; - public ExceptionHandlerMiddleware(OcelotRequestDelegate next, + public ExceptionHandlerMiddleware(RequestDelegate next, IOcelotLoggerFactory loggerFactory, - IInternalConfigurationRepository configRepo, IRequestScopedDataRepository repo) : base(loggerFactory.CreateLogger()) { - _configRepo = configRepo; - _repo = repo; _next = next; + _repo = repo; } - public async Task Invoke(DownstreamContext context) + public async Task Invoke(HttpContext httpContext) { try { - context.HttpContext.RequestAborted.ThrowIfCancellationRequested(); + httpContext.RequestAborted.ThrowIfCancellationRequested(); - //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(); + var internalConfiguration = httpContext.Items.IInternalConfiguration(); - if (configuration.IsError) - { - throw new Exception($"{MiddlewareName} setting pipeline errors. IOcelotConfigurationProvider returned {configuration.Errors.ToErrorString()}"); - } - - TrySetGlobalRequestId(context, configuration.Data); - - context.Configuration = configuration.Data; + TrySetGlobalRequestId(httpContext, internalConfiguration); Logger.LogDebug("ocelot pipeline started"); - await _next.Invoke(context); + await _next.Invoke(httpContext); } - catch (OperationCanceledException) when (context.HttpContext.RequestAborted.IsCancellationRequested) + catch (OperationCanceledException) when (httpContext.RequestAborted.IsCancellationRequested) { Logger.LogDebug("operation canceled"); - if (!context.HttpContext.Response.HasStarted) + if (!httpContext.Response.HasStarted) { - context.HttpContext.Response.StatusCode = 499; + httpContext.Response.StatusCode = 499; } } catch (Exception e) { Logger.LogDebug("error calling middleware"); - var message = CreateMessage(context, e); + var message = CreateMessage(httpContext, e); Logger.LogError(message, e); - SetInternalServerErrorOnResponse(context); + SetInternalServerErrorOnResponse(httpContext); } Logger.LogDebug("ocelot pipeline finished"); } - private void TrySetGlobalRequestId(DownstreamContext context, IInternalConfiguration configuration) + private void TrySetGlobalRequestId(HttpContext httpContext, IInternalConfiguration configuration) { var key = configuration.RequestId; - if (!string.IsNullOrEmpty(key) && context.HttpContext.Request.Headers.TryGetValue(key, out var upstreamRequestIds)) + if (!string.IsNullOrEmpty(key) && httpContext.Request.Headers.TryGetValue(key, out var upstreamRequestIds)) { - context.HttpContext.TraceIdentifier = upstreamRequestIds.First(); + httpContext.TraceIdentifier = upstreamRequestIds.First(); } - _repo.Add("RequestId", context.HttpContext.TraceIdentifier); + _repo.Add("RequestId", httpContext.TraceIdentifier); } - private void SetInternalServerErrorOnResponse(DownstreamContext context) + private void SetInternalServerErrorOnResponse(HttpContext httpContext) { - if (!context.HttpContext.Response.HasStarted) + if (!httpContext.Response.HasStarted) { - context.HttpContext.Response.StatusCode = 500; + httpContext.Response.StatusCode = 500; } } - private string CreateMessage(DownstreamContext context, Exception e) + private string CreateMessage(HttpContext httpContext, Exception e) { var message = $"Exception caught in global error handler, exception message: {e.Message}, exception stack: {e.StackTrace}"; @@ -107,7 +94,7 @@ namespace Ocelot.Errors.Middleware $"{message}, inner exception message {e.InnerException.Message}, inner exception stack {e.InnerException.StackTrace}"; } - return $"{message} RequestId: {context.HttpContext.TraceIdentifier}"; + return $"{message} RequestId: {httpContext.TraceIdentifier}"; } } } diff --git a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddlewareExtensions.cs b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddlewareExtensions.cs index 82e1050e..8f2e8819 100644 --- a/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddlewareExtensions.cs +++ b/src/Ocelot/Errors/Middleware/ExceptionHandlerMiddlewareExtensions.cs @@ -1,13 +1,13 @@ -using Microsoft.AspNetCore.Builder; -using Ocelot.Middleware.Pipeline; - -namespace Ocelot.Errors.Middleware -{ - public static class ExceptionHandlerMiddlewareExtensions - { - public static IOcelotPipelineBuilder UseExceptionHandlerMiddleware(this IOcelotPipelineBuilder builder) - { - return builder.UseMiddleware(); - } - } +using Microsoft.AspNetCore.Builder; +using Ocelot.Middleware; + +namespace Ocelot.Errors.Middleware +{ + public static class ExceptionHandlerMiddlewareExtensions + { + public static IApplicationBuilder UseExceptionHandlerMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } } diff --git a/src/Ocelot/Errors/OcelotErrorCode.cs b/src/Ocelot/Errors/OcelotErrorCode.cs index 2ca44e70..46864181 100644 --- a/src/Ocelot/Errors/OcelotErrorCode.cs +++ b/src/Ocelot/Errors/OcelotErrorCode.cs @@ -1,47 +1,47 @@ -namespace Ocelot.Errors -{ - public enum OcelotErrorCode - { +namespace Ocelot.Errors +{ + public enum OcelotErrorCode + { UnauthenticatedError = 0, - UnknownError = 1, - DownstreampathTemplateAlreadyUsedError = 2, - UnableToFindDownstreamRouteError = 3, - CannotAddDataError = 4, - CannotFindDataError = 5, - UnableToCompleteRequestError = 6, - UnableToCreateAuthenticationHandlerError = 7, - UnsupportedAuthenticationProviderError = 8, - CannotFindClaimError = 9, - ParsingConfigurationHeaderError = 10, - NoInstructionsError = 11, - InstructionNotForClaimsError = 12, - UnauthorizedError = 13, - ClaimValueNotAuthorisedError = 14, - ScopeNotAuthorisedError = 15, - UserDoesNotHaveClaimError = 16, - DownstreamPathTemplateContainsSchemeError = 17, - DownstreamPathNullOrEmptyError = 18, - DownstreamSchemeNullOrEmptyError = 19, - DownstreamHostNullOrEmptyError = 20, - ServicesAreNullError = 21, - ServicesAreEmptyError = 22, - UnableToFindServiceDiscoveryProviderError = 23, - UnableToFindLoadBalancerError = 24, - RequestTimedOutError = 25, - UnableToFindQoSProviderError = 26, - UnmappableRequestError = 27, - RateLimitOptionsError = 28, - PathTemplateDoesntStartWithForwardSlash = 29, - FileValidationFailedError = 30, - UnableToFindDelegatingHandlerProviderError = 31, - CouldNotFindPlaceholderError = 32, - CouldNotFindAggregatorError = 33, - CannotAddPlaceholderError = 34, - CannotRemovePlaceholderError = 35, + UnknownError = 1, + DownstreampathTemplateAlreadyUsedError = 2, + UnableToFindDownstreamRouteError = 3, + CannotAddDataError = 4, + CannotFindDataError = 5, + UnableToCompleteRequestError = 6, + UnableToCreateAuthenticationHandlerError = 7, + UnsupportedAuthenticationProviderError = 8, + CannotFindClaimError = 9, + ParsingConfigurationHeaderError = 10, + NoInstructionsError = 11, + InstructionNotForClaimsError = 12, + UnauthorizedError = 13, + ClaimValueNotAuthorisedError = 14, + ScopeNotAuthorisedError = 15, + UserDoesNotHaveClaimError = 16, + DownstreamPathTemplateContainsSchemeError = 17, + DownstreamPathNullOrEmptyError = 18, + DownstreamSchemeNullOrEmptyError = 19, + DownstreamHostNullOrEmptyError = 20, + ServicesAreNullError = 21, + ServicesAreEmptyError = 22, + UnableToFindServiceDiscoveryProviderError = 23, + UnableToFindLoadBalancerError = 24, + RequestTimedOutError = 25, + UnableToFindQoSProviderError = 26, + UnmappableRequestError = 27, + RateLimitOptionsError = 28, + PathTemplateDoesntStartWithForwardSlash = 29, + FileValidationFailedError = 30, + UnableToFindDelegatingHandlerProviderError = 31, + CouldNotFindPlaceholderError = 32, + CouldNotFindAggregatorError = 33, + CannotAddPlaceholderError = 34, + CannotRemovePlaceholderError = 35, QuotaExceededError = 36, RequestCanceled = 37, - ConnectionToDownstreamServiceError = 38, - CouldNotFindLoadBalancerCreator = 39, - ErrorInvokingLoadBalancerCreator = 40, - } + ConnectionToDownstreamServiceError = 38, + CouldNotFindLoadBalancerCreator = 39, + ErrorInvokingLoadBalancerCreator = 40, + } } diff --git a/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs b/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs index a6992bf1..cab97084 100644 --- a/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs +++ b/src/Ocelot/Headers/HttpResponseHeaderReplacer.cs @@ -7,6 +7,8 @@ namespace Ocelot.Headers using Ocelot.Responses; using System.Collections.Generic; using System.Linq; + using Microsoft.AspNetCore.Http; + using Ocelot.DownstreamRouteFinder.Middleware; public class HttpResponseHeaderReplacer : IHttpResponseHeaderReplacer { @@ -17,10 +19,10 @@ namespace Ocelot.Headers _placeholders = placeholders; } - public Response Replace(DownstreamContext context, List fAndRs) + public Response Replace(HttpContext httpContext, List fAndRs) { - var response = context.DownstreamResponse; - var request = context.DownstreamRequest; + var response = httpContext.Items.DownstreamResponse(); + var request = httpContext.Items.DownstreamRequest(); foreach (var f in fAndRs) { diff --git a/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs b/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs index 5ef5c84c..afe37774 100644 --- a/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs +++ b/src/Ocelot/Headers/IHttpResponseHeaderReplacer.cs @@ -1,12 +1,12 @@ -namespace Ocelot.Headers -{ - using Ocelot.Configuration; - using Ocelot.Middleware; +namespace Ocelot.Headers +{ + using Ocelot.Configuration; using Ocelot.Responses; - using System.Collections.Generic; - - public interface IHttpResponseHeaderReplacer - { - Response Replace(DownstreamContext context, List fAndRs); - } + using System.Collections.Generic; + using Microsoft.AspNetCore.Http; + + public interface IHttpResponseHeaderReplacer + { + public Response Replace(HttpContext httpContext, List fAndRs); + } } diff --git a/src/Ocelot/Headers/Middleware/ClaimsToHeadersMiddleware.cs b/src/Ocelot/Headers/Middleware/ClaimsToHeadersMiddleware.cs index e45bb4bb..5d2bed0a 100644 --- a/src/Ocelot/Headers/Middleware/ClaimsToHeadersMiddleware.cs +++ b/src/Ocelot/Headers/Middleware/ClaimsToHeadersMiddleware.cs @@ -1,44 +1,50 @@ -using Ocelot.Logging; -using Ocelot.Middleware; -using System.Linq; -using System.Threading.Tasks; +namespace Ocelot.Headers.Middleware +{ + using Microsoft.AspNetCore.Http; + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.Logging; + using Ocelot.Middleware; + using System.Linq; + using System.Threading.Tasks; + + public class ClaimsToHeadersMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IAddHeadersToRequest _addHeadersToRequest; + + public ClaimsToHeadersMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IAddHeadersToRequest addHeadersToRequest) + : base(loggerFactory.CreateLogger()) + { + _next = next; + _addHeadersToRequest = addHeadersToRequest; + } + + public async Task Invoke(HttpContext httpContext) + { + var downstreamReRoute = httpContext.Items.DownstreamReRoute(); + + if (downstreamReRoute.ClaimsToHeaders.Any()) + { + Logger.LogInformation($"{downstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to headers"); -namespace Ocelot.Headers.Middleware -{ - public class ClaimsToHeadersMiddleware : OcelotMiddleware - { - private readonly OcelotRequestDelegate _next; - private readonly IAddHeadersToRequest _addHeadersToRequest; - - public ClaimsToHeadersMiddleware(OcelotRequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IAddHeadersToRequest addHeadersToRequest) - : base(loggerFactory.CreateLogger()) - { - _next = next; - _addHeadersToRequest = addHeadersToRequest; - } - - public async Task Invoke(DownstreamContext context) - { - if (context.DownstreamReRoute.ClaimsToHeaders.Any()) - { - Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to headers"); - - var response = _addHeadersToRequest.SetHeadersOnDownstreamRequest(context.DownstreamReRoute.ClaimsToHeaders, context.HttpContext.User.Claims, context.DownstreamRequest); - - if (response.IsError) - { - Logger.LogWarning("Error setting headers on context, setting pipeline error"); - - SetPipelineError(context, response.Errors); - return; - } - - Logger.LogInformation("headers have been set on context"); - } - - await _next.Invoke(context); - } - } -} + var downstreamRequest = httpContext.Items.DownstreamRequest(); + + var response = _addHeadersToRequest.SetHeadersOnDownstreamRequest(downstreamReRoute.ClaimsToHeaders, httpContext.User.Claims, downstreamRequest); + + if (response.IsError) + { + Logger.LogWarning("Error setting headers on context, setting pipeline error"); + + httpContext.Items.UpsertErrors(response.Errors); + return; + } + + Logger.LogInformation("headers have been set on context"); + } + + await _next.Invoke(httpContext); + } + } +} diff --git a/src/Ocelot/Headers/Middleware/ClaimsToHeadersMiddlewareExtensions.cs b/src/Ocelot/Headers/Middleware/ClaimsToHeadersMiddlewareExtensions.cs index 91a5fe90..4923c27f 100644 --- a/src/Ocelot/Headers/Middleware/ClaimsToHeadersMiddlewareExtensions.cs +++ b/src/Ocelot/Headers/Middleware/ClaimsToHeadersMiddlewareExtensions.cs @@ -1,11 +1,10 @@ using Microsoft.AspNetCore.Builder; -using Ocelot.Middleware.Pipeline; namespace Ocelot.Headers.Middleware { public static class ClaimsToHeadersMiddlewareExtensions { - public static IOcelotPipelineBuilder UseClaimsToHeadersMiddleware(this IOcelotPipelineBuilder builder) + public static IApplicationBuilder UseClaimsToHeadersMiddleware(this IApplicationBuilder builder) { return builder.UseMiddleware(); } diff --git a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs index 79684385..3affff06 100644 --- a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs +++ b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddleware.cs @@ -1,23 +1,26 @@ -using Ocelot.Logging; -using Ocelot.Middleware; -using System.Threading.Tasks; - namespace Ocelot.Headers.Middleware { + using Microsoft.AspNetCore.Http; + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.Logging; + using Ocelot.Middleware; + using System.Threading.Tasks; + public class HttpHeadersTransformationMiddleware : OcelotMiddleware { - private readonly OcelotRequestDelegate _next; + private readonly RequestDelegate _next; private readonly IHttpContextRequestHeaderReplacer _preReplacer; private readonly IHttpResponseHeaderReplacer _postReplacer; private readonly IAddHeadersToResponse _addHeadersToResponse; private readonly IAddHeadersToRequest _addHeadersToRequest; - public HttpHeadersTransformationMiddleware(OcelotRequestDelegate next, + public HttpHeadersTransformationMiddleware(RequestDelegate next, IOcelotLoggerFactory loggerFactory, IHttpContextRequestHeaderReplacer preReplacer, IHttpResponseHeaderReplacer postReplacer, IAddHeadersToResponse addHeadersToResponse, - IAddHeadersToRequest addHeadersToRequest) + IAddHeadersToRequest addHeadersToRequest + ) : base(loggerFactory.CreateLogger()) { _addHeadersToResponse = addHeadersToResponse; @@ -27,27 +30,33 @@ namespace Ocelot.Headers.Middleware _preReplacer = preReplacer; } - public async Task Invoke(DownstreamContext context) + public async Task Invoke(HttpContext httpContext) { - var preFAndRs = context.DownstreamReRoute.UpstreamHeadersFindAndReplace; + var downstreamReRoute = httpContext.Items.DownstreamReRoute(); + + var preFAndRs = downstreamReRoute.UpstreamHeadersFindAndReplace; //todo - this should be on httprequestmessage not httpcontext? - _preReplacer.Replace(context.HttpContext, preFAndRs); + _preReplacer.Replace(httpContext, preFAndRs); - _addHeadersToRequest.SetHeadersOnDownstreamRequest(context.DownstreamReRoute.AddHeadersToUpstream, context.HttpContext); + _addHeadersToRequest.SetHeadersOnDownstreamRequest(downstreamReRoute.AddHeadersToUpstream, httpContext); - await _next.Invoke(context); + await _next.Invoke(httpContext); - if (context.IsError) + // todo check errors is ok + //todo put this check on the base class? + if (httpContext.Items.Errors().Count > 0) { return; } - var postFAndRs = context.DownstreamReRoute.DownstreamHeadersFindAndReplace; + var postFAndRs = downstreamReRoute.DownstreamHeadersFindAndReplace; - _postReplacer.Replace(context, postFAndRs); + _postReplacer.Replace(httpContext, postFAndRs); - _addHeadersToResponse.Add(context.DownstreamReRoute.AddHeadersToDownstream, context.DownstreamResponse); + var downstreamResponse = httpContext.Items.DownstreamResponse(); + + _addHeadersToResponse.Add(downstreamReRoute.AddHeadersToDownstream, downstreamResponse); } } } diff --git a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddlewareExtensions.cs b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddlewareExtensions.cs index 4cfec462..4c44d923 100644 --- a/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddlewareExtensions.cs +++ b/src/Ocelot/Headers/Middleware/HttpHeadersTransformationMiddlewareExtensions.cs @@ -1,13 +1,12 @@ -using Microsoft.AspNetCore.Builder; -using Ocelot.Middleware.Pipeline; - -namespace Ocelot.Headers.Middleware -{ - public static class HttpHeadersTransformationMiddlewareExtensions - { - public static IOcelotPipelineBuilder UseHttpHeadersTransformationMiddleware(this IOcelotPipelineBuilder builder) - { - return builder.UseMiddleware(); - } - } +namespace Ocelot.Headers.Middleware +{ + using Microsoft.AspNetCore.Builder; + + public static class HttpHeadersTransformationMiddlewareExtensions + { + public static IApplicationBuilder UseHttpHeadersTransformationMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } } diff --git a/src/Ocelot/Infrastructure/CannotAddPlaceholderError.cs b/src/Ocelot/Infrastructure/CannotAddPlaceholderError.cs index 5ff14542..28bdbf7a 100644 --- a/src/Ocelot/Infrastructure/CannotAddPlaceholderError.cs +++ b/src/Ocelot/Infrastructure/CannotAddPlaceholderError.cs @@ -1,12 +1,12 @@ -using Ocelot.Errors; - -namespace Ocelot.Infrastructure -{ - public class CannotAddPlaceholderError : Error - { - public CannotAddPlaceholderError(string message) - : base(message, OcelotErrorCode.CannotAddPlaceholderError) - { - } - } +using Ocelot.Errors; + +namespace Ocelot.Infrastructure +{ + public class CannotAddPlaceholderError : Error + { + public CannotAddPlaceholderError(string message) + : base(message, OcelotErrorCode.CannotAddPlaceholderError, 404) + { + } + } } diff --git a/src/Ocelot/Infrastructure/CannotRemovePlaceholderError.cs b/src/Ocelot/Infrastructure/CannotRemovePlaceholderError.cs index 335a8da1..ddaa5c09 100644 --- a/src/Ocelot/Infrastructure/CannotRemovePlaceholderError.cs +++ b/src/Ocelot/Infrastructure/CannotRemovePlaceholderError.cs @@ -5,8 +5,8 @@ namespace Ocelot.Infrastructure public class CannotRemovePlaceholderError : Error { public CannotRemovePlaceholderError(string message) - : base(message, OcelotErrorCode.CannotRemovePlaceholderError) + : base(message, OcelotErrorCode.CannotRemovePlaceholderError, 404) { } } -} \ No newline at end of file +} diff --git a/src/Ocelot/Infrastructure/Claims/Parser/CannotFindClaimError.cs b/src/Ocelot/Infrastructure/Claims/Parser/CannotFindClaimError.cs index 302bba06..df66f340 100644 --- a/src/Ocelot/Infrastructure/Claims/Parser/CannotFindClaimError.cs +++ b/src/Ocelot/Infrastructure/Claims/Parser/CannotFindClaimError.cs @@ -1,12 +1,12 @@ -namespace Ocelot.Infrastructure.Claims.Parser -{ - using Errors; - - public class CannotFindClaimError : Error - { +namespace Ocelot.Infrastructure.Claims.Parser +{ + using Ocelot.Errors; + + public class CannotFindClaimError : Error + { public CannotFindClaimError(string message) - : base(message, OcelotErrorCode.CannotFindClaimError) - { - } - } + : base(message, OcelotErrorCode.CannotFindClaimError, 403) + { + } + } } diff --git a/src/Ocelot/Infrastructure/CouldNotFindPlaceholderError.cs b/src/Ocelot/Infrastructure/CouldNotFindPlaceholderError.cs index 5c7121aa..20bb8833 100644 --- a/src/Ocelot/Infrastructure/CouldNotFindPlaceholderError.cs +++ b/src/Ocelot/Infrastructure/CouldNotFindPlaceholderError.cs @@ -5,7 +5,7 @@ namespace Ocelot.Infrastructure public class CouldNotFindPlaceholderError : Error { public CouldNotFindPlaceholderError(string placeholder) - : base($"Unable to find placeholder called {placeholder}", OcelotErrorCode.CouldNotFindPlaceholderError) + : base($"Unable to find placeholder called {placeholder}", OcelotErrorCode.CouldNotFindPlaceholderError, 404) { } } diff --git a/src/Ocelot/Infrastructure/RequestData/CannotAddDataError.cs b/src/Ocelot/Infrastructure/RequestData/CannotAddDataError.cs index e411f0d4..d186435a 100644 --- a/src/Ocelot/Infrastructure/RequestData/CannotAddDataError.cs +++ b/src/Ocelot/Infrastructure/RequestData/CannotAddDataError.cs @@ -1,11 +1,11 @@ -using Ocelot.Errors; - -namespace Ocelot.Infrastructure.RequestData -{ - public class CannotAddDataError : Error - { - public CannotAddDataError(string message) : base(message, OcelotErrorCode.CannotAddDataError) - { - } - } +using Ocelot.Errors; + +namespace Ocelot.Infrastructure.RequestData +{ + public class CannotAddDataError : Error + { + public CannotAddDataError(string message) : base(message, OcelotErrorCode.CannotAddDataError, 404) + { + } + } } diff --git a/src/Ocelot/Infrastructure/RequestData/CannotFindDataError.cs b/src/Ocelot/Infrastructure/RequestData/CannotFindDataError.cs index 907742df..27bdc1f9 100644 --- a/src/Ocelot/Infrastructure/RequestData/CannotFindDataError.cs +++ b/src/Ocelot/Infrastructure/RequestData/CannotFindDataError.cs @@ -1,11 +1,11 @@ -using Ocelot.Errors; - -namespace Ocelot.Infrastructure.RequestData -{ - public class CannotFindDataError : Error - { - public CannotFindDataError(string message) : base(message, OcelotErrorCode.CannotFindDataError) - { - } - } +using Ocelot.Errors; + +namespace Ocelot.Infrastructure.RequestData +{ + public class CannotFindDataError : Error + { + public CannotFindDataError(string message) : base(message, OcelotErrorCode.CannotFindDataError, 404) + { + } + } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/CookieStickySessions.cs b/src/Ocelot/LoadBalancer/LoadBalancers/CookieStickySessions.cs index 775ed7ce..1c9fb062 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/CookieStickySessions.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/CookieStickySessions.cs @@ -2,11 +2,12 @@ namespace Ocelot.LoadBalancer.LoadBalancers { using Ocelot.Infrastructure; using Ocelot.Middleware; - using Responses; + using Ocelot.Responses; using System; using System.Collections.Concurrent; using System.Threading.Tasks; - using Values; + using Microsoft.AspNetCore.Http; + using Ocelot.Values; public class CookieStickySessions : ILoadBalancer { @@ -41,9 +42,9 @@ namespace Ocelot.LoadBalancer.LoadBalancers }); } - public async Task> Lease(DownstreamContext context) + public async Task> Lease(HttpContext httpContext) { - var key = context.HttpContext.Request.Cookies[_key]; + var key = httpContext.Request.Cookies[_key]; lock (_lock) { @@ -61,7 +62,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers } } - var next = await _loadBalancer.Lease(context); + var next = await _loadBalancer.Lease(httpContext); if (next.IsError) { diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/CouldNotFindLoadBalancerCreator.cs b/src/Ocelot/LoadBalancer/LoadBalancers/CouldNotFindLoadBalancerCreator.cs index 9c95e239..44a4a9bc 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/CouldNotFindLoadBalancerCreator.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/CouldNotFindLoadBalancerCreator.cs @@ -5,7 +5,7 @@ public class CouldNotFindLoadBalancerCreator : Error { public CouldNotFindLoadBalancerCreator(string message) - : base(message, OcelotErrorCode.CouldNotFindLoadBalancerCreator) + : base(message, OcelotErrorCode.CouldNotFindLoadBalancerCreator, 404) { } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ErrorInvokingLoadBalancerCreator.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ErrorInvokingLoadBalancerCreator.cs index 022814c5..aaeda5f4 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ErrorInvokingLoadBalancerCreator.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ErrorInvokingLoadBalancerCreator.cs @@ -5,7 +5,7 @@ public class ErrorInvokingLoadBalancerCreator : Error { - public ErrorInvokingLoadBalancerCreator(Exception e) : base($"Error when invoking user provided load balancer creator function, Message: {e.Message}, StackTrace: {e.StackTrace}", OcelotErrorCode.ErrorInvokingLoadBalancerCreator) + public ErrorInvokingLoadBalancerCreator(Exception e) : base($"Error when invoking user provided load balancer creator function, Message: {e.Message}, StackTrace: {e.StackTrace}", OcelotErrorCode.ErrorInvokingLoadBalancerCreator, 500) { } } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs index 4f8ec2b7..7879e4c6 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ILoadBalancer.cs @@ -1,13 +1,13 @@ -using Ocelot.Middleware; -using Ocelot.Responses; -using Ocelot.Values; -using System.Threading.Tasks; - namespace Ocelot.LoadBalancer.LoadBalancers { + using Microsoft.AspNetCore.Http; + using Ocelot.Responses; + using Ocelot.Values; + using System.Threading.Tasks; + public interface ILoadBalancer { - Task> Lease(DownstreamContext context); + Task> Lease(HttpContext httpContext); void Release(ServiceHostAndPort hostAndPort); } diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnection.cs b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnection.cs index ee7c0fdd..c83ecede 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnection.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/LeastConnection.cs @@ -1,13 +1,14 @@ -using Ocelot.Middleware; -using Ocelot.Responses; -using Ocelot.Values; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Ocelot.LoadBalancer.LoadBalancers +namespace Ocelot.LoadBalancer.LoadBalancers { + using Microsoft.AspNetCore.Http; + using Ocelot.Middleware; + using Ocelot.Responses; + using Ocelot.Values; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + public class LeastConnection : ILoadBalancer { private readonly Func>> _services; @@ -22,7 +23,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers _leases = new List(); } - public async Task> Lease(DownstreamContext downstreamContext) + public async Task> Lease(HttpContext httpContext) { var services = await _services.Invoke(); diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs index 112fd5bb..76e35f3b 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/NoLoadBalancer.cs @@ -1,13 +1,14 @@ -using Ocelot.Middleware; -using Ocelot.Responses; -using Ocelot.Values; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Ocelot.LoadBalancer.LoadBalancers +namespace Ocelot.LoadBalancer.LoadBalancers { + using Microsoft.AspNetCore.Http; + using Ocelot.Middleware; + using Ocelot.Responses; + using Ocelot.Values; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + public class NoLoadBalancer : ILoadBalancer { private readonly Func>> _services; @@ -17,7 +18,7 @@ namespace Ocelot.LoadBalancer.LoadBalancers _services = services; } - public async Task> Lease(DownstreamContext downstreamContext) + public async Task> Lease(HttpContext httpContext) { var services = await _services(); diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobin.cs b/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobin.cs index 492a2c57..d3204b97 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobin.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/RoundRobin.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; public class RoundRobin : ILoadBalancer { @@ -19,7 +20,7 @@ _services = services; } - public async Task> Lease(DownstreamContext downstreamContext) + public async Task> Lease(HttpContext httpContext) { var services = await _services(); lock (_lock) diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ServicesAreEmptyError.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ServicesAreEmptyError.cs index b1a1e02c..2823b53f 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ServicesAreEmptyError.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ServicesAreEmptyError.cs @@ -5,8 +5,8 @@ namespace Ocelot.LoadBalancer.LoadBalancers public class ServicesAreEmptyError : Error { public ServicesAreEmptyError(string message) - : base(message, OcelotErrorCode.ServicesAreEmptyError) + : base(message, OcelotErrorCode.ServicesAreEmptyError, 404) { } } -} \ No newline at end of file +} diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/ServicesAreNullError.cs b/src/Ocelot/LoadBalancer/LoadBalancers/ServicesAreNullError.cs index 0116bee9..498334c9 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/ServicesAreNullError.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/ServicesAreNullError.cs @@ -5,8 +5,8 @@ namespace Ocelot.LoadBalancer.LoadBalancers public class ServicesAreNullError : Error { public ServicesAreNullError(string message) - : base(message, OcelotErrorCode.ServicesAreNullError) + : base(message, OcelotErrorCode.ServicesAreNullError, 404) { } } -} \ No newline at end of file +} diff --git a/src/Ocelot/LoadBalancer/LoadBalancers/UnableToFindLoadBalancerError.cs b/src/Ocelot/LoadBalancer/LoadBalancers/UnableToFindLoadBalancerError.cs index 04a305f5..caca4d27 100644 --- a/src/Ocelot/LoadBalancer/LoadBalancers/UnableToFindLoadBalancerError.cs +++ b/src/Ocelot/LoadBalancer/LoadBalancers/UnableToFindLoadBalancerError.cs @@ -1,12 +1,12 @@ -using Ocelot.Errors; - -namespace Ocelot.LoadBalancer.LoadBalancers -{ - public class UnableToFindLoadBalancerError : Errors.Error - { +using Ocelot.Errors; + +namespace Ocelot.LoadBalancer.LoadBalancers +{ + public class UnableToFindLoadBalancerError : Errors.Error + { public UnableToFindLoadBalancerError(string message) - : base(message, OcelotErrorCode.UnableToFindLoadBalancerError) - { - } - } + : base(message, OcelotErrorCode.UnableToFindLoadBalancerError, 404) + { + } + } } diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs index bcf9fe80..56ad778e 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddleware.cs @@ -1,68 +1,78 @@ -using Ocelot.LoadBalancer.LoadBalancers; -using Ocelot.Logging; -using Ocelot.Middleware; -using System; -using System.Threading.Tasks; +namespace Ocelot.LoadBalancer.Middleware +{ + using Microsoft.AspNetCore.Http; + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.LoadBalancer.LoadBalancers; + using Ocelot.Logging; + using Ocelot.Middleware; + using System; + using System.Threading.Tasks; + + public class LoadBalancingMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly ILoadBalancerHouse _loadBalancerHouse; + + public LoadBalancingMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + ILoadBalancerHouse loadBalancerHouse) + : base(loggerFactory.CreateLogger()) + { + _next = next; + _loadBalancerHouse = loadBalancerHouse; + } + + public async Task Invoke(HttpContext httpContext) + { + var downstreamReRoute = httpContext.Items.DownstreamReRoute(); -namespace Ocelot.LoadBalancer.Middleware -{ - public class LoadBalancingMiddleware : OcelotMiddleware - { - private readonly OcelotRequestDelegate _next; - private readonly ILoadBalancerHouse _loadBalancerHouse; - - public LoadBalancingMiddleware(OcelotRequestDelegate next, - IOcelotLoggerFactory loggerFactory, - ILoadBalancerHouse loadBalancerHouse) - : base(loggerFactory.CreateLogger()) - { - _next = next; - _loadBalancerHouse = loadBalancerHouse; - } - - public async Task Invoke(DownstreamContext context) - { - var loadBalancer = _loadBalancerHouse.Get(context.DownstreamReRoute, context.Configuration.ServiceProviderConfiguration); - if (loadBalancer.IsError) - { - Logger.LogDebug("there was an error retriving the loadbalancer, setting pipeline error"); - SetPipelineError(context, loadBalancer.Errors); - return; + var internalConfiguration = httpContext.Items.IInternalConfiguration(); + + var loadBalancer = _loadBalancerHouse.Get(downstreamReRoute, internalConfiguration.ServiceProviderConfiguration); + + if (loadBalancer.IsError) + { + Logger.LogDebug("there was an error retriving the loadbalancer, setting pipeline error"); + httpContext.Items.UpsertErrors(loadBalancer.Errors); + return; + } + + var hostAndPort = await loadBalancer.Data.Lease(httpContext); + if (hostAndPort.IsError) + { + Logger.LogDebug("there was an error leasing the loadbalancer, setting pipeline error"); + httpContext.Items.UpsertErrors(hostAndPort.Errors); + return; } - var hostAndPort = await loadBalancer.Data.Lease(context); - if (hostAndPort.IsError) - { - Logger.LogDebug("there was an error leasing the loadbalancer, setting pipeline error"); - SetPipelineError(context, hostAndPort.Errors); - return; - } - - context.DownstreamRequest.Host = hostAndPort.Data.DownstreamHost; - - if (hostAndPort.Data.DownstreamPort > 0) - { - context.DownstreamRequest.Port = hostAndPort.Data.DownstreamPort; - } - - if (!string.IsNullOrEmpty(hostAndPort.Data.Scheme)) - { - context.DownstreamRequest.Scheme = hostAndPort.Data.Scheme; - } - - try - { - await _next.Invoke(context); - } - catch (Exception) - { - Logger.LogDebug("Exception calling next middleware, exception will be thrown to global handler"); - throw; - } - finally - { - loadBalancer.Data.Release(hostAndPort.Data); - } - } - } -} + var downstreamRequest = httpContext.Items.DownstreamRequest(); + + //todo check downstreamRequest is ok + downstreamRequest.Host = hostAndPort.Data.DownstreamHost; + + if (hostAndPort.Data.DownstreamPort > 0) + { + downstreamRequest.Port = hostAndPort.Data.DownstreamPort; + } + + if (!string.IsNullOrEmpty(hostAndPort.Data.Scheme)) + { + downstreamRequest.Scheme = hostAndPort.Data.Scheme; + } + + try + { + await _next.Invoke(httpContext); + } + catch (Exception) + { + Logger.LogDebug("Exception calling next middleware, exception will be thrown to global handler"); + throw; + } + finally + { + loadBalancer.Data.Release(hostAndPort.Data); + } + } + } +} diff --git a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs index 8f6558c7..91293dbc 100644 --- a/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs +++ b/src/Ocelot/LoadBalancer/Middleware/LoadBalancingMiddlewareExtensions.cs @@ -1,13 +1,12 @@ -using Microsoft.AspNetCore.Builder; -using Ocelot.Middleware.Pipeline; - -namespace Ocelot.LoadBalancer.Middleware +namespace Ocelot.LoadBalancer.Middleware { - public static class LoadBalancingMiddlewareExtensions - { - public static IOcelotPipelineBuilder UseLoadBalancingMiddleware(this IOcelotPipelineBuilder builder) - { - return builder.UseMiddleware(); - } - } + using Microsoft.AspNetCore.Builder; + + public static class LoadBalancingMiddlewareExtensions + { + public static IApplicationBuilder UseLoadBalancingMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } } diff --git a/src/Ocelot/Logging/OcelotDiagnosticListener.cs b/src/Ocelot/Logging/OcelotDiagnosticListener.cs index 09beac49..20c9dff3 100644 --- a/src/Ocelot/Logging/OcelotDiagnosticListener.cs +++ b/src/Ocelot/Logging/OcelotDiagnosticListener.cs @@ -1,11 +1,10 @@ -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DiagnosticAdapter; -using Ocelot.Middleware; -using System; - -namespace Ocelot.Logging +namespace Ocelot.Logging { + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.DiagnosticAdapter; + using System; + public class OcelotDiagnosticListener { private readonly IOcelotLogger _logger; @@ -17,27 +16,6 @@ namespace Ocelot.Logging _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) { diff --git a/src/Ocelot/Middleware/BaseUrlFinder.cs b/src/Ocelot/Middleware/BaseUrlFinder.cs index d65fb769..69a1224a 100644 --- a/src/Ocelot/Middleware/BaseUrlFinder.cs +++ b/src/Ocelot/Middleware/BaseUrlFinder.cs @@ -1,23 +1,23 @@ -using Microsoft.Extensions.Configuration; - -namespace Ocelot.Middleware -{ - public class BaseUrlFinder : IBaseUrlFinder - { - private readonly IConfiguration _config; - - public BaseUrlFinder(IConfiguration config) - { - _config = config; - } - - public string Find() - { - //tries to get base url out of file... - var baseUrl = _config.GetValue("GlobalConfiguration:BaseUrl", ""); - - //falls back to memory config then finally default.. - return string.IsNullOrEmpty(baseUrl) ? _config.GetValue("BaseUrl", "http://localhost:5000") : baseUrl; - } - } +using Microsoft.Extensions.Configuration; + +namespace Ocelot.Middleware +{ + public class BaseUrlFinder : IBaseUrlFinder + { + private readonly IConfiguration _config; + + public BaseUrlFinder(IConfiguration config) + { + _config = config; + } + + public string Find() + { + //tries to get base url out of file... + var baseUrl = _config.GetValue("GlobalConfiguration:BaseUrl", ""); + + //falls back to memory config then finally default.. + return string.IsNullOrEmpty(baseUrl) ? _config.GetValue("BaseUrl", "http://localhost:5000") : baseUrl; + } + } } diff --git a/src/Ocelot/Middleware/ConfigurationMiddleware.cs b/src/Ocelot/Middleware/ConfigurationMiddleware.cs new file mode 100644 index 00000000..fc0aafce --- /dev/null +++ b/src/Ocelot/Middleware/ConfigurationMiddleware.cs @@ -0,0 +1,37 @@ +namespace Ocelot.Middleware +{ + using System.Threading.Tasks; + using Ocelot.Errors.Middleware; + using Ocelot.Configuration.Repository; + using Ocelot.Logging; + using Microsoft.AspNetCore.Http; + using Ocelot.DownstreamRouteFinder.Middleware; + + public class ConfigurationMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IInternalConfigurationRepository _configRepo; + + public ConfigurationMiddleware(RequestDelegate next, IOcelotLoggerFactory loggerFactory, IInternalConfigurationRepository configRepo) + : base(loggerFactory.CreateLogger()) + { + _next = next; + _configRepo = configRepo; + } + + public async Task Invoke(HttpContext httpContext) + { + //todo check the config is actually ok? + var config = _configRepo.Get(); + + if(config.IsError) + { + throw new System.Exception("OOOOPS this should not happen raise an issue in GitHub"); + } + + httpContext.Items.SetIInternalConfiguration(config.Data); + + await _next.Invoke(httpContext); + } + } +} diff --git a/src/Ocelot/Middleware/DownstreamContext.cs b/src/Ocelot/Middleware/DownstreamContext.cs deleted file mode 100644 index 5a2dc34a..00000000 --- a/src/Ocelot/Middleware/DownstreamContext.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Errors; -using Ocelot.Request.Middleware; -using System.Collections.Generic; - -namespace Ocelot.Middleware -{ - public class DownstreamContext - { - public DownstreamContext(HttpContext httpContext) - { - HttpContext = httpContext; - Errors = new List(); - } - - public List TemplatePlaceholderNameAndValues { get; set; } - - public HttpContext HttpContext { get; } - - public DownstreamReRoute DownstreamReRoute { get; set; } - - public DownstreamRequest DownstreamRequest { get; set; } - - public DownstreamResponse DownstreamResponse { get; set; } - - public List Errors { get; } - - public IInternalConfiguration Configuration { get; set; } - - public bool IsError => Errors.Count > 0; - } -} diff --git a/src/Ocelot/Middleware/DownstreamContextMiddlewareExtensions.cs b/src/Ocelot/Middleware/DownstreamContextMiddlewareExtensions.cs new file mode 100644 index 00000000..2533bdc6 --- /dev/null +++ b/src/Ocelot/Middleware/DownstreamContextMiddlewareExtensions.cs @@ -0,0 +1,12 @@ +namespace Ocelot.Middleware +{ + using Microsoft.AspNetCore.Builder; + + public static class DownstreamContextMiddlewareExtensions + { + public static IApplicationBuilder UseDownstreamContextMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/src/Ocelot/Middleware/HttpItemsExtensions.cs b/src/Ocelot/Middleware/HttpItemsExtensions.cs new file mode 100644 index 00000000..290e9383 --- /dev/null +++ b/src/Ocelot/Middleware/HttpItemsExtensions.cs @@ -0,0 +1,117 @@ +namespace Ocelot.Middleware +{ + using Ocelot.Configuration; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Errors; + using Ocelot.Request.Middleware; + using System.Collections.Generic; + + public static class HttpItemsExtensions + { + public static void UpsertDownstreamRequest(this IDictionary input, DownstreamRequest downstreamRequest) + { + input.Upsert("DownstreamRequest", downstreamRequest); + } + + public static void UpsertDownstreamResponse(this IDictionary input, DownstreamResponse downstreamResponse) + { + input.Upsert("DownstreamResponse", downstreamResponse); + } + + public static void UpsertDownstreamReRoute(this IDictionary input, DownstreamReRoute downstreamReRoute) + { + input.Upsert("DownstreamReRoute", downstreamReRoute); + } + + public static void UpsertTemplatePlaceholderNameAndValues(this IDictionary input, List tPNV) + { + input.Upsert("TemplatePlaceholderNameAndValues", tPNV); + } + + public static void UpsertDownstreamRoute(this IDictionary input, DownstreamRoute downstreamRoute) + { + input.Upsert("DownstreamRoute", downstreamRoute); + } + + public static void UpsertErrors(this IDictionary input, List errors) + { + input.Upsert("Errors", errors); + } + + public static void SetError(this IDictionary input, Error error) + { + var errors = new List() { error }; + input.Upsert("Errors", errors); + } + + public static void SetIInternalConfiguration(this IDictionary input, IInternalConfiguration config) + { + input.Upsert("IInternalConfiguration", config); + } + + public static IInternalConfiguration IInternalConfiguration(this IDictionary input) + { + return input.Get("IInternalConfiguration"); + } + + public static List Errors(this IDictionary input) + { + var errors = input.Get>("Errors"); + return errors == null ? new List() : errors; + } + + public static DownstreamRoute DownstreamRoute(this IDictionary input) + { + return input.Get("DownstreamRoute"); + } + + public static List TemplatePlaceholderNameAndValues(this IDictionary input) + { + return input.Get>("TemplatePlaceholderNameAndValues"); + } + + public static DownstreamRequest DownstreamRequest(this IDictionary input) + { + return input.Get("DownstreamRequest"); + } + + public static DownstreamResponse DownstreamResponse(this IDictionary input) + { + return input.Get("DownstreamResponse"); + } + + public static DownstreamReRoute DownstreamReRoute(this IDictionary input) + { + return input.Get("DownstreamReRoute"); + } + + private static T Get(this IDictionary input, string key) + { + if (input.TryGetValue(key, out var value)) + { + return (T)value; + } + + return default(T); + } + + private static void Upsert(this IDictionary input, string key, T value) + { + if (input.DoesntExist(key)) + { + input.Add(key, value); + } + else + { + input.Remove(key); + input.Add(key, value); + } + } + + private static bool DoesntExist(this IDictionary input, string key) + { + return !input.ContainsKey(key); + } + } +} diff --git a/src/Ocelot/Middleware/Multiplexer/IDefinedAggregator.cs b/src/Ocelot/Middleware/Multiplexer/IDefinedAggregator.cs deleted file mode 100644 index 48234782..00000000 --- a/src/Ocelot/Middleware/Multiplexer/IDefinedAggregator.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Ocelot.Middleware.Multiplexer -{ - public interface IDefinedAggregator - { - Task Aggregate(List responses); - } -} diff --git a/src/Ocelot/Middleware/Multiplexer/IMultiplexer.cs b/src/Ocelot/Middleware/Multiplexer/IMultiplexer.cs deleted file mode 100644 index 57a724e2..00000000 --- a/src/Ocelot/Middleware/Multiplexer/IMultiplexer.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Ocelot.Configuration; -using System.Threading.Tasks; - -namespace Ocelot.Middleware.Multiplexer -{ - public interface IMultiplexer - { - Task Multiplex(DownstreamContext context, ReRoute reRoute, OcelotRequestDelegate next); - } -} diff --git a/src/Ocelot/Middleware/Multiplexer/IResponseAggregator.cs b/src/Ocelot/Middleware/Multiplexer/IResponseAggregator.cs deleted file mode 100644 index bd56ca09..00000000 --- a/src/Ocelot/Middleware/Multiplexer/IResponseAggregator.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Ocelot.Configuration; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Ocelot.Middleware.Multiplexer -{ - public interface IResponseAggregator - { - Task Aggregate(ReRoute reRoute, DownstreamContext originalContext, List downstreamResponses); - } -} diff --git a/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs b/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs deleted file mode 100644 index a49e20c1..00000000 --- a/src/Ocelot/Middleware/Multiplexer/Multiplexer.cs +++ /dev/null @@ -1,152 +0,0 @@ -using Ocelot.Configuration; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Ocelot.Middleware.Multiplexer -{ - public class Multiplexer : IMultiplexer - { - private readonly IResponseAggregatorFactory _factory; - - public Multiplexer(IResponseAggregatorFactory factory) - { - _factory = factory; - } - - public async Task Multiplex(DownstreamContext context, ReRoute reRoute, OcelotRequestDelegate next) - { - var reRouteKeysConfigs = reRoute.DownstreamReRouteConfig; - if (reRouteKeysConfigs == null || !reRouteKeysConfigs.Any()) - { - var tasks = new Task[reRoute.DownstreamReRoute.Count]; - - for (var i = 0; i < reRoute.DownstreamReRoute.Count; i++) - { - var downstreamContext = new DownstreamContext(context.HttpContext) - { - TemplatePlaceholderNameAndValues = context.TemplatePlaceholderNameAndValues, - Configuration = context.Configuration, - DownstreamReRoute = reRoute.DownstreamReRoute[i], - }; - - tasks[i] = Fire(downstreamContext, next); - } - - await Task.WhenAll(tasks); - - var contexts = new List(); - - foreach (var task in tasks) - { - var finished = await task; - contexts.Add(finished); - } - - await Map(reRoute, context, contexts); - } - else - { - var downstreamContextMain = new DownstreamContext(context.HttpContext) - { - TemplatePlaceholderNameAndValues = context.TemplatePlaceholderNameAndValues, - Configuration = context.Configuration, - DownstreamReRoute = reRoute.DownstreamReRoute[0], - }; - var mainResponse = await Fire(downstreamContextMain, next); - - if (reRoute.DownstreamReRoute.Count == 1) - { - MapNotAggregate(context, new List() { mainResponse }); - return; - } - - var tasks = new List>(); - if (mainResponse.DownstreamResponse == null) - { - return; - } - - var content = await mainResponse.DownstreamResponse.Content.ReadAsStringAsync(); - var jObject = Newtonsoft.Json.Linq.JToken.Parse(content); - - for (var i = 1; i < reRoute.DownstreamReRoute.Count; i++) - { - var templatePlaceholderNameAndValues = context.TemplatePlaceholderNameAndValues; - var downstreamReRoute = reRoute.DownstreamReRoute[i]; - var matchAdvancedAgg = reRouteKeysConfigs.FirstOrDefault(q => q.ReRouteKey == downstreamReRoute.Key); - if (matchAdvancedAgg != null) - { - var values = jObject.SelectTokens(matchAdvancedAgg.JsonPath).Select(s => s.ToString()).Distinct().ToList(); - - foreach (var value in values) - { - var downstreamContext = new DownstreamContext(context.HttpContext) - { - TemplatePlaceholderNameAndValues = new List(templatePlaceholderNameAndValues), - Configuration = context.Configuration, - DownstreamReRoute = downstreamReRoute, - }; - downstreamContext.TemplatePlaceholderNameAndValues.Add(new PlaceholderNameAndValue("{" + matchAdvancedAgg.Parameter + "}", value.ToString())); - tasks.Add(Fire(downstreamContext, next)); - } - } - else - { - var downstreamContext = new DownstreamContext(context.HttpContext) - { - TemplatePlaceholderNameAndValues = new List(templatePlaceholderNameAndValues), - Configuration = context.Configuration, - DownstreamReRoute = downstreamReRoute, - }; - tasks.Add(Fire(downstreamContext, next)); - } - } - - await Task.WhenAll(tasks); - - var contexts = new List() { mainResponse }; - - foreach (var task in tasks) - { - var finished = await task; - contexts.Add(finished); - } - - await Map(reRoute, context, contexts); - } - } - - private async Task Map(ReRoute reRoute, DownstreamContext context, List contexts) - { - if (reRoute.DownstreamReRoute.Count > 1) - { - var aggregator = _factory.Get(reRoute); - await aggregator.Aggregate(reRoute, context, contexts); - } - else - { - MapNotAggregate(context, contexts); - } - } - - private void MapNotAggregate(DownstreamContext originalContext, List downstreamContexts) - { - //assume at least one..if this errors then it will be caught by global exception handler - var finished = downstreamContexts.First(); - - originalContext.Errors.AddRange(finished.Errors); - - originalContext.DownstreamRequest = finished.DownstreamRequest; - - originalContext.DownstreamResponse = finished.DownstreamResponse; - } - - private async Task Fire(DownstreamContext context, OcelotRequestDelegate next) - { - await next.Invoke(context); - return context; - } - } -} diff --git a/src/Ocelot/Middleware/OcelotMiddleware.cs b/src/Ocelot/Middleware/OcelotMiddleware.cs index 0bf57afc..269c4e70 100644 --- a/src/Ocelot/Middleware/OcelotMiddleware.cs +++ b/src/Ocelot/Middleware/OcelotMiddleware.cs @@ -1,33 +1,16 @@ -using Ocelot.Errors; -using Ocelot.Logging; -using System.Collections.Generic; - -namespace Ocelot.Middleware -{ - public abstract class OcelotMiddleware +namespace Ocelot.Middleware +{ + using Ocelot.Logging; + + public abstract class OcelotMiddleware { - protected OcelotMiddleware(IOcelotLogger logger) - { - Logger = logger; - MiddlewareName = this.GetType().Name; - } - - public IOcelotLogger Logger { get; } - - public string MiddlewareName { get; } - - public void SetPipelineError(DownstreamContext context, List errors) - { - foreach (var error in errors) - { - SetPipelineError(context, error); - } - } - - public void SetPipelineError(DownstreamContext context, Error error) - { - Logger.LogWarning(error.Message); - context.Errors.Add(error); - } - } + protected OcelotMiddleware(IOcelotLogger logger) + { + Logger = logger; + MiddlewareName = GetType().Name; + } + + public IOcelotLogger Logger { get; } + public string MiddlewareName { get; } + } } diff --git a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs index 42bc6b71..98ea6960 100644 --- a/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs +++ b/src/Ocelot/Middleware/OcelotMiddlewareExtensions.cs @@ -1,6 +1,6 @@ namespace Ocelot.Middleware { - using DependencyInjection; + using Ocelot.DependencyInjection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; @@ -11,7 +11,6 @@ using Ocelot.Configuration.Repository; using Ocelot.Configuration.Setter; using Ocelot.Logging; - using Ocelot.Middleware.Pipeline; using Ocelot.Responses; using System; using System.Diagnostics; @@ -42,37 +41,25 @@ return CreateOcelotPipeline(builder, pipelineConfiguration); } - public static Task UseOcelot(this IApplicationBuilder app, Action builderAction) + public static Task UseOcelot(this IApplicationBuilder app, Action builderAction) => UseOcelot(app, builderAction, new OcelotPipelineConfiguration()); - public static async Task UseOcelot(this IApplicationBuilder app, Action builderAction, OcelotPipelineConfiguration configuration) + public static async Task UseOcelot(this IApplicationBuilder app, Action builderAction, OcelotPipelineConfiguration configuration) { - await CreateConfiguration(app); // initConfiguration + await CreateConfiguration(app); ConfigureDiagnosticListener(app); - var ocelotPipelineBuilder = new OcelotPipelineBuilder(app.ApplicationServices); - builderAction?.Invoke(ocelotPipelineBuilder, configuration ?? new OcelotPipelineConfiguration()); + builderAction?.Invoke(app, configuration ?? new OcelotPipelineConfiguration()); - var ocelotDelegate = ocelotPipelineBuilder.Build(); app.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware"; - app.Use(async (context, task) => - { - var downstreamContext = new DownstreamContext(context); - await ocelotDelegate.Invoke(downstreamContext); - }); - return app; } private static IApplicationBuilder CreateOcelotPipeline(IApplicationBuilder builder, OcelotPipelineConfiguration pipelineConfiguration) { - var pipelineBuilder = new OcelotPipelineBuilder(builder.ApplicationServices); - - pipelineBuilder.BuildOcelotPipeline(pipelineConfiguration); - - var firstDelegate = pipelineBuilder.Build(); + builder.BuildOcelotPipeline(pipelineConfiguration); /* inject first delegate into first piece of asp.net middleware..maybe not like this @@ -82,12 +69,6 @@ builder.Properties["analysis.NextMiddlewareName"] = "TransitionToOcelotMiddleware"; - builder.Use(async (context, task) => - { - var downstreamContext = new DownstreamContext(context); - await firstDelegate.Invoke(downstreamContext); - }); - return builder; } diff --git a/src/Ocelot/Middleware/OcelotPipelineConfiguration.cs b/src/Ocelot/Middleware/OcelotPipelineConfiguration.cs index 3dbb76a7..b1f1bf99 100644 --- a/src/Ocelot/Middleware/OcelotPipelineConfiguration.cs +++ b/src/Ocelot/Middleware/OcelotPipelineConfiguration.cs @@ -1,9 +1,10 @@ namespace Ocelot.Middleware { - using Ocelot.Middleware.Pipeline; using System; using System.Collections.Generic; using System.Threading.Tasks; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Http; public class OcelotPipelineConfiguration { @@ -12,38 +13,64 @@ /// is the next thing called in the Ocelot pipeline. Anything after next.invoke is the last thing called /// in the Ocelot pipeline before we go to the global error handler. /// - public Func, Task> PreErrorResponderMiddleware { get; set; } + /// + /// This is called after the global error handling middleware so any code before calling next.invoke + /// is the next thing called in the Ocelot pipeline. Anything after next.invoke is the last thing called + /// in the Ocelot pipeline before we go to the global error handler. + /// + public Func, Task> PreErrorResponderMiddleware { get; set; } /// /// This is to allow the user to run any extra authentication before the Ocelot authentication /// kicks in /// - public Func, Task> PreAuthenticationMiddleware { get; set; } + /// + /// This is to allow the user to run any extra authentication before the Ocelot authentication + /// kicks in + /// + public Func, Task> PreAuthenticationMiddleware { get; set; } /// /// This allows the user to completely override the ocelot authentication middleware /// - public Func, Task> AuthenticationMiddleware { get; set; } + /// + /// This allows the user to completely override the ocelot authentication middleware + /// + public Func, Task> AuthenticationMiddleware { get; set; } /// /// This is to allow the user to run any extra authorisation before the Ocelot authentication /// kicks in /// - public Func, Task> PreAuthorisationMiddleware { get; set; } + /// + /// This is to allow the user to run any extra authorisation before the Ocelot authentication + /// kicks in + /// + public Func, Task> PreAuthorisationMiddleware { get; set; } /// /// This allows the user to completely override the ocelot authorisation middleware /// - public Func, Task> AuthorisationMiddleware { get; set; } + /// + /// This allows the user to completely override the ocelot authorisation middleware + /// + public Func, Task> AuthorisationMiddleware { get; set; } /// /// This allows the user to implement there own query string manipulation logic /// - public Func, Task> PreQueryStringBuilderMiddleware { get; set; } + /// + /// This allows the user to implement there own query string manipulation logic + /// + public Func, Task> PreQueryStringBuilderMiddleware { get; set; } /// /// This is an extension that will branch to different pipes /// - public List>> MapWhenOcelotPipeline { get; } = new List>>(); + /// + /// This is an extension that will branch to different pipes + /// + // todo fix this data structure + public Dictionary, Action> MapWhenOcelotPipeline { get; } = new Dictionary, Action>(); } } diff --git a/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs b/src/Ocelot/Middleware/OcelotPipelineExtensions.cs similarity index 51% rename from src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs rename to src/Ocelot/Middleware/OcelotPipelineExtensions.cs index d52cfb0d..6158c72b 100644 --- a/src/Ocelot/Middleware/Pipeline/OcelotPipelineExtensions.cs +++ b/src/Ocelot/Middleware/OcelotPipelineExtensions.cs @@ -1,101 +1,112 @@ -using Ocelot.Authentication.Middleware; -using Ocelot.Authorisation.Middleware; -using Ocelot.Cache.Middleware; -using Ocelot.Claims.Middleware; -using Ocelot.DownstreamRouteFinder.Middleware; -using Ocelot.DownstreamUrlCreator.Middleware; -using Ocelot.Errors.Middleware; -using Ocelot.Headers.Middleware; -using Ocelot.LoadBalancer.Middleware; -using Ocelot.PathManipulation.Middleware; -using Ocelot.QueryStrings.Middleware; -using Ocelot.RateLimit.Middleware; -using Ocelot.Request.Middleware; -using Ocelot.Requester.Middleware; -using Ocelot.RequestId.Middleware; -using Ocelot.Responder.Middleware; -using Ocelot.Security.Middleware; -using Ocelot.WebSockets.Middleware; -using System; -using System.Threading.Tasks; - -namespace Ocelot.Middleware.Pipeline +namespace Ocelot.Middleware { + using Ocelot.QueryStrings.Middleware; + using Ocelot.RateLimit.Middleware; + using Ocelot.Request.Middleware; + using Ocelot.Requester.Middleware; + using Ocelot.RequestId.Middleware; + using Ocelot.Responder.Middleware; + using Ocelot.Security.Middleware; + using Ocelot.Authentication.Middleware; + using Ocelot.Authorisation.Middleware; + using Ocelot.Cache.Middleware; + using Ocelot.Claims.Middleware; + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.DownstreamUrlCreator.Middleware; + using Ocelot.Errors.Middleware; + using Ocelot.Headers.Middleware; + using Ocelot.LoadBalancer.Middleware; + using System; + using System.Threading.Tasks; + using Ocelot.DownstreamPathManipulation.Middleware; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Http; + using Ocelot.WebSockets.Middleware; + using Ocelot.Multiplexer; + public static class OcelotPipelineExtensions { - public static OcelotRequestDelegate BuildOcelotPipeline(this IOcelotPipelineBuilder builder, + public static RequestDelegate BuildOcelotPipeline(this IApplicationBuilder app, OcelotPipelineConfiguration pipelineConfiguration) { + // this sets up the downstream context and gets the config + app.UseDownstreamContextMiddleware(); + // This is registered to catch any global exceptions that are not handled // It also sets the Request Id if anything is set globally - builder.UseExceptionHandlerMiddleware(); + app.UseExceptionHandlerMiddleware(); // If the request is for websockets upgrade we fork into a different pipeline - builder.MapWhen(context => context.HttpContext.WebSockets.IsWebSocketRequest, - app => + app.MapWhen(httpContext => httpContext.WebSockets.IsWebSocketRequest, + wenSocketsApp => { - app.UseDownstreamRouteFinderMiddleware(); - app.UseDownstreamRequestInitialiser(); - app.UseLoadBalancingMiddleware(); - app.UseDownstreamUrlCreatorMiddleware(); - app.UseWebSocketsProxyMiddleware(); + wenSocketsApp.UseDownstreamRouteFinderMiddleware(); + wenSocketsApp.UseMultiplexingMiddleware(); + wenSocketsApp.UseDownstreamRequestInitialiser(); + wenSocketsApp.UseLoadBalancingMiddleware(); + wenSocketsApp.UseDownstreamUrlCreatorMiddleware(); + wenSocketsApp.UseWebSocketsProxyMiddleware(); }); // Allow the user to respond with absolutely anything they want. - builder.UseIfNotNull(pipelineConfiguration.PreErrorResponderMiddleware); + app.UseIfNotNull(pipelineConfiguration.PreErrorResponderMiddleware); // This is registered first so it can catch any errors and issue an appropriate response - builder.UseResponderMiddleware(); + app.UseResponderMiddleware(); // Then we get the downstream route information - builder.UseDownstreamRouteFinderMiddleware(); + app.UseDownstreamRouteFinderMiddleware(); + + // Multiplex the request if required + app.UseMultiplexingMiddleware(); // This security module, IP whitelist blacklist, extended security mechanism - builder.UseSecurityMiddleware(); + app.UseSecurityMiddleware(); //Expand other branch pipes if (pipelineConfiguration.MapWhenOcelotPipeline != null) { foreach (var pipeline in pipelineConfiguration.MapWhenOcelotPipeline) { - builder.MapWhen(pipeline); + // todo why is this asking for an app app? + app.MapWhen(pipeline.Key, pipeline.Value); } } // Now we have the ds route we can transform headers and stuff? - builder.UseHttpHeadersTransformationMiddleware(); + app.UseHttpHeadersTransformationMiddleware(); // Initialises downstream request - builder.UseDownstreamRequestInitialiser(); + app.UseDownstreamRequestInitialiser(); // We check whether the request is ratelimit, and if there is no continue processing - builder.UseRateLimiting(); + app.UseRateLimiting(); // This adds or updates the request id (initally we try and set this based on global config in the error handling middleware) // If anything was set at global level and we have a different setting at re route level the global stuff will be overwritten // This means you can get a scenario where you have a different request id from the first piece of middleware to the request id middleware. - builder.UseRequestIdMiddleware(); + app.UseRequestIdMiddleware(); // Allow pre authentication logic. The idea being people might want to run something custom before what is built in. - builder.UseIfNotNull(pipelineConfiguration.PreAuthenticationMiddleware); + app.UseIfNotNull(pipelineConfiguration.PreAuthenticationMiddleware); // Now we know where the client is going to go we can authenticate them. // We allow the ocelot middleware to be overriden by whatever the // user wants if (pipelineConfiguration.AuthenticationMiddleware == null) { - builder.UseAuthenticationMiddleware(); + app.UseAuthenticationMiddleware(); } else { - builder.Use(pipelineConfiguration.AuthenticationMiddleware); + app.Use(pipelineConfiguration.AuthenticationMiddleware); } // The next thing we do is look at any claims transforms in case this is important for authorisation - builder.UseClaimsToClaimsMiddleware(); + app.UseClaimsToClaimsMiddleware(); // Allow pre authorisation logic. The idea being people might want to run something custom before what is built in. - builder.UseIfNotNull(pipelineConfiguration.PreAuthorisationMiddleware); + app.UseIfNotNull(pipelineConfiguration.PreAuthorisationMiddleware); // Now we have authenticated and done any claims transformation we // can authorise the request @@ -103,42 +114,42 @@ namespace Ocelot.Middleware.Pipeline // user wants if (pipelineConfiguration.AuthorisationMiddleware == null) { - builder.UseAuthorisationMiddleware(); + app.UseAuthorisationMiddleware(); } else { - builder.Use(pipelineConfiguration.AuthorisationMiddleware); + app.Use(pipelineConfiguration.AuthorisationMiddleware); } // Now we can run the claims to headers transformation middleware - builder.UseClaimsToHeadersMiddleware(); + app.UseClaimsToHeadersMiddleware(); // Allow the user to implement their own query string manipulation logic - builder.UseIfNotNull(pipelineConfiguration.PreQueryStringBuilderMiddleware); + app.UseIfNotNull(pipelineConfiguration.PreQueryStringBuilderMiddleware); // Now we can run any claims to query string transformation middleware - builder.UseClaimsToQueryStringMiddleware(); + app.UseClaimsToQueryStringMiddleware(); - builder.UseClaimsToDownstreamPathMiddleware(); + app.UseClaimsToDownstreamPathMiddleware(); // Get the load balancer for this request - builder.UseLoadBalancingMiddleware(); + app.UseLoadBalancingMiddleware(); // This takes the downstream route we retrieved earlier and replaces any placeholders with the variables that should be used - builder.UseDownstreamUrlCreatorMiddleware(); + app.UseDownstreamUrlCreatorMiddleware(); // Not sure if this is the best place for this but we use the downstream url // as the basis for our cache key. - builder.UseOutputCacheMiddleware(); + app.UseOutputCacheMiddleware(); //We fire off the request and set the response on the scoped data repo - builder.UseHttpRequesterMiddleware(); + app.UseHttpRequesterMiddleware(); - return builder.Build(); + return app.Build(); } - private static void UseIfNotNull(this IOcelotPipelineBuilder builder, - Func, Task> middleware) + private static void UseIfNotNull(this IApplicationBuilder builder, + Func, Task> middleware) { if (middleware != null) { diff --git a/src/Ocelot/Middleware/OcelotRequestDelegate.cs b/src/Ocelot/Middleware/OcelotRequestDelegate.cs deleted file mode 100644 index 130dfd86..00000000 --- a/src/Ocelot/Middleware/OcelotRequestDelegate.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.Threading.Tasks; - -namespace Ocelot.Middleware -{ - public delegate Task OcelotRequestDelegate(DownstreamContext downstreamContext); -} diff --git a/src/Ocelot/Middleware/Pipeline/IOcelotPipelineBuilder.cs b/src/Ocelot/Middleware/Pipeline/IOcelotPipelineBuilder.cs deleted file mode 100644 index b7199e2b..00000000 --- a/src/Ocelot/Middleware/Pipeline/IOcelotPipelineBuilder.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Removed code and changed RequestDelete to OcelotRequestDelete, HttpContext to DownstreamContext, removed some exception handling messages - -using System; - -namespace Ocelot.Middleware.Pipeline -{ - public interface IOcelotPipelineBuilder - { - IServiceProvider ApplicationServices { get; } - - IOcelotPipelineBuilder Use(Func middleware); - - OcelotRequestDelegate Build(); - - IOcelotPipelineBuilder New(); - } -} diff --git a/src/Ocelot/Middleware/Pipeline/LICENSE.txt b/src/Ocelot/Middleware/Pipeline/LICENSE.txt deleted file mode 100644 index 7b2956ec..00000000 --- a/src/Ocelot/Middleware/Pipeline/LICENSE.txt +++ /dev/null @@ -1,14 +0,0 @@ -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -this file except in compliance with the License. You may obtain a copy of the -License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed -under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -CONDITIONS OF ANY KIND, either express or implied. See the License for the -specific language governing permissions and limitations under the License. diff --git a/src/Ocelot/Middleware/Pipeline/MapWhenMiddleware.cs b/src/Ocelot/Middleware/Pipeline/MapWhenMiddleware.cs deleted file mode 100644 index f05c35e4..00000000 --- a/src/Ocelot/Middleware/Pipeline/MapWhenMiddleware.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace Ocelot.Middleware.Pipeline -{ - public class MapWhenMiddleware - { - private readonly OcelotRequestDelegate _next; - private readonly MapWhenOptions _options; - - public MapWhenMiddleware(OcelotRequestDelegate next, MapWhenOptions options) - { - if (next == null) - { - throw new ArgumentNullException(nameof(next)); - } - - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - _next = next; - _options = options; - } - - public async Task Invoke(DownstreamContext context) - { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (_options.Predicate(context)) - { - await _options.Branch(context); - } - else - { - await _next(context); - } - } - } -} diff --git a/src/Ocelot/Middleware/Pipeline/MapWhenOptions.cs b/src/Ocelot/Middleware/Pipeline/MapWhenOptions.cs deleted file mode 100644 index 912688c3..00000000 --- a/src/Ocelot/Middleware/Pipeline/MapWhenOptions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -namespace Ocelot.Middleware.Pipeline -{ - public class MapWhenOptions - { - private Func _predicate; - - public Func Predicate - { - get - { - return _predicate; - } - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - _predicate = value; - } - } - - public OcelotRequestDelegate Branch { get; set; } - } -} diff --git a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilder.cs b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilder.cs deleted file mode 100644 index 5e3ee32e..00000000 --- a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilder.cs +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Removed code and changed RequestDelete to OcelotRequestDelete, HttpContext to DownstreamContext, removed some exception handling messages - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Ocelot.Middleware.Pipeline -{ - public class OcelotPipelineBuilder : IOcelotPipelineBuilder - { - private readonly IList> _middlewares; - - public OcelotPipelineBuilder(IServiceProvider provider) - { - ApplicationServices = provider; - _middlewares = new List>(); - } - - public OcelotPipelineBuilder(IOcelotPipelineBuilder builder) - { - ApplicationServices = builder.ApplicationServices; - _middlewares = new List>(); - } - - public IServiceProvider ApplicationServices { get; } - - public IOcelotPipelineBuilder Use(Func middleware) - { - _middlewares.Add(middleware); - return this; - } - - public OcelotRequestDelegate Build() - { - OcelotRequestDelegate app = context => - { - context.HttpContext.Response.StatusCode = 404; - return Task.CompletedTask; - }; - - foreach (var component in _middlewares.Reverse()) - { - app = component(app); - } - - return app; - } - - public IOcelotPipelineBuilder New() - { - return new OcelotPipelineBuilder(this); - } - } -} diff --git a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs b/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs deleted file mode 100644 index 3dbc4a48..00000000 --- a/src/Ocelot/Middleware/Pipeline/OcelotPipelineBuilderExtensions.cs +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -// Removed code and changed RequestDelete to OcelotRequestDelete, HttpContext to DownstreamContext, removed some exception handling messages - -using Microsoft.Extensions.DependencyInjection; -using System; -using System.Diagnostics; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Threading.Tasks; - -namespace Ocelot.Middleware.Pipeline -{ - using Predicate = Func; - - public static class OcelotPipelineBuilderExtensions - { - internal const string InvokeMethodName = "Invoke"; - internal const string InvokeAsyncMethodName = "InvokeAsync"; - private static readonly MethodInfo GetServiceInfo = typeof(OcelotPipelineBuilderExtensions).GetMethod(nameof(GetService), BindingFlags.NonPublic | BindingFlags.Static); - - public static IOcelotPipelineBuilder UseMiddleware(this IOcelotPipelineBuilder app, params object[] args) - { - return app.UseMiddleware(typeof(TMiddleware), args); - } - - public static IOcelotPipelineBuilder Use(this IOcelotPipelineBuilder app, Func, Task> middleware) - { - return app.Use(next => - { - return context => - { - Func simpleNext = () => next(context); - return middleware(context, simpleNext); - }; - }); - } - - public static IOcelotPipelineBuilder UseMiddleware(this IOcelotPipelineBuilder app, Type middleware, params object[] args) - { - return app.Use(next => - { - var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public); - var invokeMethods = methods.Where(m => - string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal) - || string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal) - ).ToArray(); - - if (invokeMethods.Length > 1) - { - throw new InvalidOperationException(); - } - - if (invokeMethods.Length == 0) - { - throw new InvalidOperationException(); - } - - var methodinfo = invokeMethods[0]; - if (!typeof(Task).IsAssignableFrom(methodinfo.ReturnType)) - { - throw new InvalidOperationException(); - } - - var parameters = methodinfo.GetParameters(); - if (parameters.Length == 0 || parameters[0].ParameterType != typeof(DownstreamContext)) - { - throw new InvalidOperationException(); - } - - var ctorArgs = new object[args.Length + 1]; - ctorArgs[0] = next; - Array.Copy(args, 0, ctorArgs, 1, args.Length); - var instance = ActivatorUtilities.CreateInstance(app.ApplicationServices, middleware, ctorArgs); - if (parameters.Length == 1) - { - var ocelotDelegate = (OcelotRequestDelegate)methodinfo.CreateDelegate(typeof(OcelotRequestDelegate), instance); - var diagnosticListener = (DiagnosticListener)app.ApplicationServices.GetService(typeof(DiagnosticListener)); - var middlewareName = ocelotDelegate.Target.GetType().Name; - - OcelotRequestDelegate wrapped = async context => - { - try - { - Write(diagnosticListener, "Ocelot.MiddlewareStarted", middlewareName, context); - await ocelotDelegate(context); - } - catch (Exception ex) - { - WriteException(diagnosticListener, ex, "Ocelot.MiddlewareException", middlewareName, context); - throw ex; - } - finally - { - Write(diagnosticListener, "Ocelot.MiddlewareFinished", middlewareName, context); - } - }; - - return wrapped; - } - - var factory = Compile(methodinfo, parameters); - - return context => - { - var serviceProvider = context.HttpContext.RequestServices ?? app.ApplicationServices; - if (serviceProvider == null) - { - throw new InvalidOperationException(); - } - - return factory(instance, context, serviceProvider); - }; - }); - } - - private static void Write(DiagnosticListener diagnosticListener, string message, string middlewareName, DownstreamContext context) - { - if (diagnosticListener != null) - { - diagnosticListener.Write(message, new { name = middlewareName, context = context }); - } - } - - private static void WriteException(DiagnosticListener diagnosticListener, Exception exception, string message, string middlewareName, DownstreamContext context) - { - if (diagnosticListener != null) - { - diagnosticListener.Write(message, new { name = middlewareName, context = context, exception = exception }); - } - } - - public static IOcelotPipelineBuilder MapWhen(this IOcelotPipelineBuilder app, Predicate predicate, Action configuration) - { - if (app == null) - { - throw new ArgumentNullException(nameof(app)); - } - - if (predicate == null) - { - throw new ArgumentNullException(nameof(predicate)); - } - - if (configuration == null) - { - throw new ArgumentNullException(nameof(configuration)); - } - - var branchBuilder = app.New(); - configuration(branchBuilder); - var branch = branchBuilder.Build(); - - var options = new MapWhenOptions - { - Predicate = predicate, - Branch = branch, - }; - return app.Use(next => new MapWhenMiddleware(next, options).Invoke); - } - - 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); - var httpContextArg = Expression.Parameter(typeof(DownstreamContext), "downstreamContext"); - var providerArg = Expression.Parameter(typeof(IServiceProvider), "serviceProvider"); - var instanceArg = Expression.Parameter(middleware, "middleware"); - - var methodArguments = new Expression[parameters.Length]; - methodArguments[0] = httpContextArg; - for (int i = 1; i < parameters.Length; i++) - { - var parameterType = parameters[i].ParameterType; - if (parameterType.IsByRef) - { - throw new NotSupportedException(); - } - - var parameterTypeExpression = new Expression[] - { - providerArg, - Expression.Constant(parameterType, typeof(Type)) - }; - - var getServiceCall = Expression.Call(GetServiceInfo, parameterTypeExpression); - methodArguments[i] = Expression.Convert(getServiceCall, parameterType); - } - - Expression middlewareInstanceArg = instanceArg; - if (methodinfo.DeclaringType != typeof(T)) - { - middlewareInstanceArg = Expression.Convert(middlewareInstanceArg, methodinfo.DeclaringType); - } - - var body = Expression.Call(middlewareInstanceArg, methodinfo, methodArguments); - - var lambda = Expression.Lambda>(body, instanceArg, httpContextArg, providerArg); - - return lambda.Compile(); - } - - private static object GetService(IServiceProvider sp, Type type) - { - var service = sp.GetService(type); - if (service == null) - { - throw new InvalidOperationException(); - } - - return service; - } - } -} diff --git a/src/Ocelot/Middleware/UnauthenticatedError.cs b/src/Ocelot/Middleware/UnauthenticatedError.cs index f6ee444b..2c6bf2bb 100644 --- a/src/Ocelot/Middleware/UnauthenticatedError.cs +++ b/src/Ocelot/Middleware/UnauthenticatedError.cs @@ -1,11 +1,12 @@ -using Ocelot.Errors; - -namespace Ocelot.Middleware -{ - public class UnauthenticatedError : Error - { - public UnauthenticatedError(string message) : base(message, OcelotErrorCode.UnauthenticatedError) - { - } - } +namespace Ocelot.Middleware +{ + using Ocelot.Errors; + + public class UnauthenticatedError : Error + { + public UnauthenticatedError(string message) + : base(message, OcelotErrorCode.UnauthenticatedError, 401) + { + } + } } diff --git a/src/Ocelot/Middleware/Multiplexer/CouldNotFindAggregatorError.cs b/src/Ocelot/Multiplexer/CouldNotFindAggregatorError.cs similarity index 73% rename from src/Ocelot/Middleware/Multiplexer/CouldNotFindAggregatorError.cs rename to src/Ocelot/Multiplexer/CouldNotFindAggregatorError.cs index e32109b7..2024fbd9 100644 --- a/src/Ocelot/Middleware/Multiplexer/CouldNotFindAggregatorError.cs +++ b/src/Ocelot/Multiplexer/CouldNotFindAggregatorError.cs @@ -1,11 +1,11 @@ using Ocelot.Errors; -namespace Ocelot.Middleware.Multiplexer +namespace Ocelot.Multiplexer { public class CouldNotFindAggregatorError : Error { public CouldNotFindAggregatorError(string aggregator) - : base($"Could not find Aggregator: {aggregator}", OcelotErrorCode.CouldNotFindAggregatorError) + : base($"Could not find Aggregator: {aggregator}", OcelotErrorCode.CouldNotFindAggregatorError, 404) { } } diff --git a/src/Ocelot/Multiplexer/IDefinedAggregator.cs b/src/Ocelot/Multiplexer/IDefinedAggregator.cs new file mode 100644 index 00000000..48e51365 --- /dev/null +++ b/src/Ocelot/Multiplexer/IDefinedAggregator.cs @@ -0,0 +1,12 @@ +namespace Ocelot.Multiplexer +{ + using Microsoft.AspNetCore.Http; + using Ocelot.Middleware; + using System.Collections.Generic; + using System.Threading.Tasks; + + public interface IDefinedAggregator + { + Task Aggregate(List responses); + } +} diff --git a/src/Ocelot/Middleware/Multiplexer/IDefinedAggregatorProvider.cs b/src/Ocelot/Multiplexer/IDefinedAggregatorProvider.cs similarity index 81% rename from src/Ocelot/Middleware/Multiplexer/IDefinedAggregatorProvider.cs rename to src/Ocelot/Multiplexer/IDefinedAggregatorProvider.cs index 375ebc33..7d4d1fdd 100644 --- a/src/Ocelot/Middleware/Multiplexer/IDefinedAggregatorProvider.cs +++ b/src/Ocelot/Multiplexer/IDefinedAggregatorProvider.cs @@ -1,7 +1,7 @@ using Ocelot.Configuration; using Ocelot.Responses; -namespace Ocelot.Middleware.Multiplexer +namespace Ocelot.Multiplexer { public interface IDefinedAggregatorProvider { diff --git a/src/Ocelot/Multiplexer/IResponseAggregator.cs b/src/Ocelot/Multiplexer/IResponseAggregator.cs new file mode 100644 index 00000000..0ef8615b --- /dev/null +++ b/src/Ocelot/Multiplexer/IResponseAggregator.cs @@ -0,0 +1,12 @@ +namespace Ocelot.Multiplexer +{ + using Microsoft.AspNetCore.Http; + using Ocelot.Configuration; + using System.Collections.Generic; + using System.Threading.Tasks; + + public interface IResponseAggregator + { + Task Aggregate(ReRoute reRoute, HttpContext originalContext, List downstreamResponses); + } +} diff --git a/src/Ocelot/Middleware/Multiplexer/IResponseAggregatorFactory.cs b/src/Ocelot/Multiplexer/IResponseAggregatorFactory.cs similarity index 62% rename from src/Ocelot/Middleware/Multiplexer/IResponseAggregatorFactory.cs rename to src/Ocelot/Multiplexer/IResponseAggregatorFactory.cs index b032ff1f..a3a08c2f 100644 --- a/src/Ocelot/Middleware/Multiplexer/IResponseAggregatorFactory.cs +++ b/src/Ocelot/Multiplexer/IResponseAggregatorFactory.cs @@ -1,7 +1,7 @@ -using Ocelot.Configuration; - -namespace Ocelot.Middleware.Multiplexer +namespace Ocelot.Multiplexer { + using Ocelot.Configuration; + public interface IResponseAggregatorFactory { IResponseAggregator Get(ReRoute reRoute); diff --git a/src/Ocelot/Middleware/Multiplexer/InMemoryResponseAggregatorFactory.cs b/src/Ocelot/Multiplexer/InMemoryResponseAggregatorFactory.cs similarity index 94% rename from src/Ocelot/Middleware/Multiplexer/InMemoryResponseAggregatorFactory.cs rename to src/Ocelot/Multiplexer/InMemoryResponseAggregatorFactory.cs index ce426d86..64ec74fd 100644 --- a/src/Ocelot/Middleware/Multiplexer/InMemoryResponseAggregatorFactory.cs +++ b/src/Ocelot/Multiplexer/InMemoryResponseAggregatorFactory.cs @@ -1,6 +1,6 @@ using Ocelot.Configuration; -namespace Ocelot.Middleware.Multiplexer +namespace Ocelot.Multiplexer { public class InMemoryResponseAggregatorFactory : IResponseAggregatorFactory { diff --git a/src/Ocelot/Multiplexer/MultiplexingMiddleware.cs b/src/Ocelot/Multiplexer/MultiplexingMiddleware.cs new file mode 100644 index 00000000..01d6fbb5 --- /dev/null +++ b/src/Ocelot/Multiplexer/MultiplexingMiddleware.cs @@ -0,0 +1,221 @@ +namespace Ocelot.Multiplexer +{ + using Microsoft.AspNetCore.Http; + using Ocelot.Configuration; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Logging; + using Ocelot.Middleware; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + + public class MultiplexingMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IResponseAggregatorFactory _factory; + + public MultiplexingMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IResponseAggregatorFactory factory + ) + : base(loggerFactory.CreateLogger()) + { + _factory = factory; + _next = next; + } + + public async Task Invoke(HttpContext httpContext) + { + if (httpContext.WebSockets.IsWebSocketRequest) + { + //todo this is obviously stupid + httpContext.Items.UpsertDownstreamReRoute(httpContext.Items.DownstreamRoute().ReRoute.DownstreamReRoute[0]); + await _next.Invoke(httpContext); + return; + } + + var reRouteKeysConfigs = httpContext.Items.DownstreamRoute().ReRoute.DownstreamReRouteConfig; + if (reRouteKeysConfigs == null || !reRouteKeysConfigs.Any()) + { + var downstreamRoute = httpContext.Items.DownstreamRoute(); + + var tasks = new Task[downstreamRoute.ReRoute.DownstreamReRoute.Count]; + + for (var i = 0; i < downstreamRoute.ReRoute.DownstreamReRoute.Count; i++) + { + var newHttpContext = Copy(httpContext); + + newHttpContext.Items + .Add("RequestId", httpContext.Items["RequestId"]); + newHttpContext.Items + .SetIInternalConfiguration(httpContext.Items.IInternalConfiguration()); + newHttpContext.Items + .UpsertTemplatePlaceholderNameAndValues(httpContext.Items.TemplatePlaceholderNameAndValues()); + newHttpContext.Items + .UpsertDownstreamReRoute(downstreamRoute.ReRoute.DownstreamReRoute[i]); + + tasks[i] = Fire(newHttpContext, _next); + } + + await Task.WhenAll(tasks); + + var contexts = new List(); + + foreach (var task in tasks) + { + var finished = await task; + contexts.Add(finished); + } + + await Map(httpContext, downstreamRoute.ReRoute, contexts); + } + else + { + httpContext.Items.UpsertDownstreamReRoute(httpContext.Items.DownstreamRoute().ReRoute.DownstreamReRoute[0]); + var mainResponse = await Fire(httpContext, _next); + + if (httpContext.Items.DownstreamRoute().ReRoute.DownstreamReRoute.Count == 1) + { + MapNotAggregate(httpContext, new List() { mainResponse }); + return; + } + + var tasks = new List>(); + + if (mainResponse.Items.DownstreamResponse() == null) + { + return; + } + + var content = await mainResponse.Items.DownstreamResponse().Content.ReadAsStringAsync(); + + var jObject = Newtonsoft.Json.Linq.JToken.Parse(content); + + for (var i = 1; i < httpContext.Items.DownstreamRoute().ReRoute.DownstreamReRoute.Count; i++) + { + var templatePlaceholderNameAndValues = httpContext.Items.TemplatePlaceholderNameAndValues(); + + var downstreamReRoute = httpContext.Items.DownstreamRoute().ReRoute.DownstreamReRoute[i]; + + var matchAdvancedAgg = reRouteKeysConfigs + .FirstOrDefault(q => q.ReRouteKey == downstreamReRoute.Key); + + if (matchAdvancedAgg != null) + { + var values = jObject.SelectTokens(matchAdvancedAgg.JsonPath).Select(s => s.ToString()).Distinct().ToList(); + + foreach (var value in values) + { + var newHttpContext = Copy(httpContext); + + var tPNV = httpContext.Items.TemplatePlaceholderNameAndValues(); + tPNV.Add(new PlaceholderNameAndValue("{" + matchAdvancedAgg.Parameter + "}", value.ToString())); + + newHttpContext.Items + .Add("RequestId", httpContext.Items["RequestId"]); + + newHttpContext.Items + .SetIInternalConfiguration(httpContext.Items.IInternalConfiguration()); + + newHttpContext.Items + .UpsertTemplatePlaceholderNameAndValues(tPNV); + + newHttpContext.Items + .UpsertDownstreamReRoute(downstreamReRoute); + + tasks.Add(Fire(newHttpContext, _next)); + } + } + else + { + var newHttpContext = Copy(httpContext); + + newHttpContext.Items + .Add("RequestId", httpContext.Items["RequestId"]); + + newHttpContext.Items + .SetIInternalConfiguration(httpContext.Items.IInternalConfiguration()); + + newHttpContext.Items + .UpsertTemplatePlaceholderNameAndValues(templatePlaceholderNameAndValues); + + newHttpContext.Items + .UpsertDownstreamReRoute(downstreamReRoute); + + tasks.Add(Fire(newHttpContext, _next)); + } + } + + await Task.WhenAll(tasks); + + var contexts = new List() { mainResponse }; + + foreach (var task in tasks) + { + var finished = await task; + contexts.Add(finished); + } + + await Map(httpContext, httpContext.Items.DownstreamRoute().ReRoute, contexts); + } + } + + private HttpContext Copy(HttpContext source) + { + var target = new DefaultHttpContext(); + + foreach (var header in source.Request.Headers) + { + target.Request.Headers.TryAdd(header.Key, header.Value); + } + + target.Request.Body = source.Request.Body; + target.Request.ContentLength = source.Request.ContentLength; + target.Request.ContentType = source.Request.ContentType; + target.Request.Host = source.Request.Host; + target.Request.Method = source.Request.Method; + target.Request.Path = source.Request.Path; + target.Request.PathBase = source.Request.PathBase; + target.Request.Protocol = source.Request.Protocol; + target.Request.Query = source.Request.Query; + target.Request.QueryString = source.Request.QueryString; + target.Request.Scheme = source.Request.Scheme; + target.Request.IsHttps = source.Request.IsHttps; + target.Request.RouteValues = source.Request.RouteValues; + target.Connection.RemoteIpAddress = source.Connection.RemoteIpAddress; + target.RequestServices = source.RequestServices; + return target; + } + + private async Task Map(HttpContext httpContext, ReRoute reRoute, List contexts) + { + if (reRoute.DownstreamReRoute.Count > 1) + { + var aggregator = _factory.Get(reRoute); + await aggregator.Aggregate(reRoute, httpContext, contexts); + } + else + { + MapNotAggregate(httpContext, contexts); + } + } + + private void MapNotAggregate(HttpContext httpContext, List downstreamContexts) + { + //assume at least one..if this errors then it will be caught by global exception handler + var finished = downstreamContexts.First(); + + httpContext.Items.UpsertErrors(finished.Items.Errors()); + + httpContext.Items.UpsertDownstreamRequest(finished.Items.DownstreamRequest()); + + httpContext.Items.UpsertDownstreamResponse(finished.Items.DownstreamResponse()); + } + + private async Task Fire(HttpContext httpContext, RequestDelegate next) + { + await next.Invoke(httpContext); + return httpContext; + } + } +} diff --git a/src/Ocelot/Multiplexer/MultiplexingMiddlewareExtensions.cs b/src/Ocelot/Multiplexer/MultiplexingMiddlewareExtensions.cs new file mode 100644 index 00000000..3e5304a1 --- /dev/null +++ b/src/Ocelot/Multiplexer/MultiplexingMiddlewareExtensions.cs @@ -0,0 +1,12 @@ +namespace Ocelot.Multiplexer +{ + using Microsoft.AspNetCore.Builder; + + public static class MultiplexingMiddlewareExtensions + { + public static IApplicationBuilder UseMultiplexingMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } +} diff --git a/src/Ocelot/Middleware/Multiplexer/ServiceLocatorDefinedAggregatorProvider.cs b/src/Ocelot/Multiplexer/ServiceLocatorDefinedAggregatorProvider.cs similarity index 94% rename from src/Ocelot/Middleware/Multiplexer/ServiceLocatorDefinedAggregatorProvider.cs rename to src/Ocelot/Multiplexer/ServiceLocatorDefinedAggregatorProvider.cs index c02a4bf7..6029e30e 100644 --- a/src/Ocelot/Middleware/Multiplexer/ServiceLocatorDefinedAggregatorProvider.cs +++ b/src/Ocelot/Multiplexer/ServiceLocatorDefinedAggregatorProvider.cs @@ -1,29 +1,29 @@ -using Microsoft.Extensions.DependencyInjection; -using Ocelot.Configuration; -using Ocelot.Responses; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Ocelot.Middleware.Multiplexer -{ - public class ServiceLocatorDefinedAggregatorProvider : IDefinedAggregatorProvider - { - private readonly Dictionary _aggregators; - - public ServiceLocatorDefinedAggregatorProvider(IServiceProvider services) - { - _aggregators = services.GetServices().ToDictionary(x => x.GetType().Name); - } - - public Response Get(ReRoute reRoute) - { - if (_aggregators.ContainsKey(reRoute.Aggregator)) - { - return new OkResponse(_aggregators[reRoute.Aggregator]); - } - - return new ErrorResponse(new CouldNotFindAggregatorError(reRoute.Aggregator)); - } - } -} +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Configuration; +using Ocelot.Responses; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ocelot.Multiplexer +{ + public class ServiceLocatorDefinedAggregatorProvider : IDefinedAggregatorProvider + { + private readonly Dictionary _aggregators; + + public ServiceLocatorDefinedAggregatorProvider(IServiceProvider services) + { + _aggregators = services.GetServices().ToDictionary(x => x.GetType().Name); + } + + public Response Get(ReRoute reRoute) + { + if (_aggregators.ContainsKey(reRoute.Aggregator)) + { + return new OkResponse(_aggregators[reRoute.Aggregator]); + } + + return new ErrorResponse(new CouldNotFindAggregatorError(reRoute.Aggregator)); + } + } +} diff --git a/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs b/src/Ocelot/Multiplexer/SimpleJsonResponseAggregator.cs similarity index 60% rename from src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs rename to src/Ocelot/Multiplexer/SimpleJsonResponseAggregator.cs index f03b52c5..52d0800b 100644 --- a/src/Ocelot/Middleware/Multiplexer/SimpleJsonResponseAggregator.cs +++ b/src/Ocelot/Multiplexer/SimpleJsonResponseAggregator.cs @@ -1,4 +1,6 @@ -using Ocelot.Configuration; +using Microsoft.AspNetCore.Http; +using Ocelot.Configuration; +using Ocelot.Middleware; using System.Collections.Generic; using System.Linq; using System.Net; @@ -7,35 +9,35 @@ using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; -namespace Ocelot.Middleware.Multiplexer +namespace Ocelot.Multiplexer { public class SimpleJsonResponseAggregator : IResponseAggregator { - public async Task Aggregate(ReRoute reRoute, DownstreamContext originalContext, List downstreamContexts) + public async Task Aggregate(ReRoute reRoute, HttpContext originalContext, List downstreamContexts) { await MapAggregateContent(originalContext, downstreamContexts); } - private static async Task MapAggregateContent(DownstreamContext originalContext, List downstreamContexts) + private static async Task MapAggregateContent(HttpContext originalContext, List downstreamContexts) { var contentBuilder = new StringBuilder(); contentBuilder.Append("{"); - var responseKeys = downstreamContexts.Select(s => s.DownstreamReRoute.Key).Distinct().ToList(); + var responseKeys = downstreamContexts.Select(s => s.Items.DownstreamReRoute().Key).Distinct().ToList(); for (var k = 0; k < responseKeys.Count; k++) { - var contexts = downstreamContexts.Where(w => w.DownstreamReRoute.Key == responseKeys[k]).ToList(); + var contexts = downstreamContexts.Where(w => w.Items.DownstreamReRoute().Key == responseKeys[k]).ToList(); if (contexts.Count == 1) { - if (contexts[0].IsError) + if (contexts[0].Items.Errors().Count > 0) { MapAggregateError(originalContext, contexts[0]); return; } - var content = await contexts[0].DownstreamResponse.Content.ReadAsStringAsync(); + var content = await contexts[0].Items.DownstreamResponse().Content.ReadAsStringAsync(); contentBuilder.Append($"\"{responseKeys[k]}\":{content}"); } else @@ -45,13 +47,13 @@ namespace Ocelot.Middleware.Multiplexer for (var i = 0; i < contexts.Count; i++) { - if (contexts[i].IsError) + if (contexts[i].Items.Errors().Count > 0) { MapAggregateError(originalContext, contexts[i]); return; } - var content = await contexts[i].DownstreamResponse.Content.ReadAsStringAsync(); + var content = await contexts[i].Items.DownstreamResponse().Content.ReadAsStringAsync(); if (string.IsNullOrWhiteSpace(content)) { continue; @@ -81,13 +83,13 @@ namespace Ocelot.Middleware.Multiplexer Headers = { ContentType = new MediaTypeHeaderValue("application/json") } }; - originalContext.DownstreamResponse = new DownstreamResponse(stringContent, HttpStatusCode.OK, new List>>(), "cannot return from aggregate..which reason phrase would you use?"); + originalContext.Items.UpsertDownstreamResponse(new DownstreamResponse(stringContent, HttpStatusCode.OK, new List>>(), "cannot return from aggregate..which reason phrase would you use?")); } - private static void MapAggregateError(DownstreamContext originalContext, DownstreamContext downstreamContext) + private static void MapAggregateError(HttpContext originalContext, HttpContext downstreamContext) { - originalContext.Errors.AddRange(downstreamContext.Errors); - originalContext.DownstreamResponse = downstreamContext.DownstreamResponse; + originalContext.Items.UpsertErrors(downstreamContext.Items.Errors()); + originalContext.Items.UpsertDownstreamResponse(downstreamContext.Items.DownstreamResponse()); } } } diff --git a/src/Ocelot/Middleware/Multiplexer/UserDefinedResponseAggregator.cs b/src/Ocelot/Multiplexer/UserDefinedResponseAggregator.cs similarity index 55% rename from src/Ocelot/Middleware/Multiplexer/UserDefinedResponseAggregator.cs rename to src/Ocelot/Multiplexer/UserDefinedResponseAggregator.cs index 47c07c00..28f32f18 100644 --- a/src/Ocelot/Middleware/Multiplexer/UserDefinedResponseAggregator.cs +++ b/src/Ocelot/Multiplexer/UserDefinedResponseAggregator.cs @@ -1,9 +1,11 @@ -using Ocelot.Configuration; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Ocelot.Middleware.Multiplexer +namespace Ocelot.Multiplexer { + using Microsoft.AspNetCore.Http; + using Ocelot.Configuration; + using Ocelot.Middleware; + using System.Collections.Generic; + using System.Threading.Tasks; + public class UserDefinedResponseAggregator : IResponseAggregator { private readonly IDefinedAggregatorProvider _provider; @@ -13,7 +15,7 @@ namespace Ocelot.Middleware.Multiplexer _provider = provider; } - public async Task Aggregate(ReRoute reRoute, DownstreamContext originalContext, List downstreamResponses) + public async Task Aggregate(ReRoute reRoute, HttpContext originalContext, List downstreamResponses) { var aggregator = _provider.Get(reRoute); @@ -22,11 +24,11 @@ namespace Ocelot.Middleware.Multiplexer var aggregateResponse = await aggregator.Data .Aggregate(downstreamResponses); - originalContext.DownstreamResponse = aggregateResponse; + originalContext.Items.UpsertDownstreamResponse(aggregateResponse); } else { - originalContext.Errors.AddRange(aggregator.Errors); + originalContext.Items.UpsertErrors(aggregator.Errors); } } } diff --git a/src/Ocelot/Ocelot.csproj b/src/Ocelot/Ocelot.csproj index bf8a4a6b..8f4f981d 100644 --- a/src/Ocelot/Ocelot.csproj +++ b/src/Ocelot/Ocelot.csproj @@ -24,12 +24,12 @@ - - - + + + NU1701 - + all diff --git a/src/Ocelot/QueryStrings/Middleware/ClaimsToQueryStringMiddleware.cs b/src/Ocelot/QueryStrings/Middleware/ClaimsToQueryStringMiddleware.cs index 4e1a1218..d8426e98 100644 --- a/src/Ocelot/QueryStrings/Middleware/ClaimsToQueryStringMiddleware.cs +++ b/src/Ocelot/QueryStrings/Middleware/ClaimsToQueryStringMiddleware.cs @@ -1,42 +1,48 @@ -namespace Ocelot.QueryStrings.Middleware -{ - using Ocelot.Logging; - using Ocelot.Middleware; - using System.Linq; - using System.Threading.Tasks; +namespace Ocelot.QueryStrings.Middleware +{ + using Ocelot.Logging; + using Ocelot.Middleware; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Ocelot.DownstreamRouteFinder.Middleware; - public class ClaimsToQueryStringMiddleware : OcelotMiddleware - { - private readonly OcelotRequestDelegate _next; - private readonly IAddQueriesToRequest _addQueriesToRequest; + public class ClaimsToQueryStringMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IAddQueriesToRequest _addQueriesToRequest; + + public ClaimsToQueryStringMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IAddQueriesToRequest addQueriesToRequest) + : base(loggerFactory.CreateLogger()) + { + _next = next; + _addQueriesToRequest = addQueriesToRequest; + } + + public async Task Invoke(HttpContext httpContext) + { + var downstreamReRoute = httpContext.Items.DownstreamReRoute(); + + if (downstreamReRoute.ClaimsToQueries.Any()) + { + Logger.LogInformation($"{downstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to queries"); - public ClaimsToQueryStringMiddleware(OcelotRequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IAddQueriesToRequest addQueriesToRequest) - : base(loggerFactory.CreateLogger()) - { - _next = next; - _addQueriesToRequest = addQueriesToRequest; - } - - public async Task Invoke(DownstreamContext context) - { - if (context.DownstreamReRoute.ClaimsToQueries.Any()) - { - Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} has instructions to convert claims to queries"); - - var response = _addQueriesToRequest.SetQueriesOnDownstreamRequest(context.DownstreamReRoute.ClaimsToQueries, context.HttpContext.User.Claims, context.DownstreamRequest); - - if (response.IsError) - { - Logger.LogWarning("there was an error setting queries on context, setting pipeline error"); - - SetPipelineError(context, response.Errors); - return; - } - } - - await _next.Invoke(context); - } - } -} + var downstreamRequest = httpContext.Items.DownstreamRequest(); + + var response = _addQueriesToRequest.SetQueriesOnDownstreamRequest(downstreamReRoute.ClaimsToQueries, httpContext.User.Claims, downstreamRequest); + + if (response.IsError) + { + Logger.LogWarning("there was an error setting queries on context, setting pipeline error"); + + httpContext.Items.UpsertErrors(response.Errors); + return; + } + } + + await _next.Invoke(httpContext); + } + } +} diff --git a/src/Ocelot/QueryStrings/Middleware/ClaimsToQueryStringMiddlewareExtensions.cs b/src/Ocelot/QueryStrings/Middleware/ClaimsToQueryStringMiddlewareExtensions.cs index e54dd976..6650efcf 100644 --- a/src/Ocelot/QueryStrings/Middleware/ClaimsToQueryStringMiddlewareExtensions.cs +++ b/src/Ocelot/QueryStrings/Middleware/ClaimsToQueryStringMiddlewareExtensions.cs @@ -1,11 +1,10 @@ namespace Ocelot.QueryStrings.Middleware { using Microsoft.AspNetCore.Builder; - using Ocelot.Middleware.Pipeline; public static class ClaimsToQueryStringMiddlewareExtensions { - public static IOcelotPipelineBuilder UseClaimsToQueryStringMiddleware(this IOcelotPipelineBuilder builder) + public static IApplicationBuilder UseClaimsToQueryStringMiddleware(this IApplicationBuilder builder) { return builder.UseMiddleware(); } diff --git a/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs index 5437ca11..76d01e94 100644 --- a/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs +++ b/src/Ocelot/RateLimit/Middleware/ClientRateLimitMiddleware.cs @@ -1,149 +1,156 @@ -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration; -using Ocelot.Logging; -using Ocelot.Middleware; -using System.Linq; -using System.Threading.Tasks; - -namespace Ocelot.RateLimit.Middleware -{ - public class ClientRateLimitMiddleware : OcelotMiddleware - { - private readonly OcelotRequestDelegate _next; - private readonly IRateLimitCounterHandler _counterHandler; - private readonly ClientRateLimitProcessor _processor; - - public ClientRateLimitMiddleware(OcelotRequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IRateLimitCounterHandler counterHandler) - : base(loggerFactory.CreateLogger()) - { - _next = next; - _counterHandler = counterHandler; - _processor = new ClientRateLimitProcessor(counterHandler); - } - - public async Task Invoke(DownstreamContext context) - { - var options = context.DownstreamReRoute.RateLimitOptions; +namespace Ocelot.RateLimit.Middleware +{ + using Microsoft.AspNetCore.Http; + using Ocelot.Configuration; + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.Logging; + using Ocelot.Middleware; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Threading.Tasks; + + public class ClientRateLimitMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly ClientRateLimitProcessor _processor; + + public ClientRateLimitMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IRateLimitCounterHandler counterHandler) + : base(loggerFactory.CreateLogger()) + { + _next = next; + _processor = new ClientRateLimitProcessor(counterHandler); + } + + public async Task Invoke(HttpContext httpContext) + { + var downstreamReRoute = httpContext.Items.DownstreamReRoute(); + + var options = downstreamReRoute.RateLimitOptions; // check if rate limiting is enabled - if (!context.DownstreamReRoute.EnableEndpointEndpointRateLimiting) - { - Logger.LogInformation($"EndpointRateLimiting is not enabled for {context.DownstreamReRoute.DownstreamPathTemplate.Value}"); - await _next.Invoke(context); - return; - } - - // compute identity from request - var identity = SetIdentity(context.HttpContext, options); - - // check white list - if (IsWhitelisted(identity, options)) - { - Logger.LogInformation($"{context.DownstreamReRoute.DownstreamPathTemplate.Value} is white listed from rate limiting"); - await _next.Invoke(context); - return; - } - - var rule = options.RateLimitRule; - if (rule.Limit > 0) - { - // increment counter - var counter = _processor.ProcessRequest(identity, options); - - // check if limit is reached - if (counter.TotalRequests > rule.Limit) - { - //compute retry after value - var retryAfter = _processor.RetryAfterFrom(counter.Timestamp, rule); - - // log blocked request - LogBlockedRequest(context.HttpContext, identity, counter, rule, context.DownstreamReRoute); - + if (!downstreamReRoute.EnableEndpointEndpointRateLimiting) + { + Logger.LogInformation($"EndpointRateLimiting is not enabled for {downstreamReRoute.DownstreamPathTemplate.Value}"); + await _next.Invoke(httpContext); + return; + } + + // compute identity from request + var identity = SetIdentity(httpContext, options); + + // check white list + if (IsWhitelisted(identity, options)) + { + Logger.LogInformation($"{downstreamReRoute.DownstreamPathTemplate.Value} is white listed from rate limiting"); + await _next.Invoke(httpContext); + return; + } + + var rule = options.RateLimitRule; + if (rule.Limit > 0) + { + // increment counter + var counter = _processor.ProcessRequest(identity, options); + + // check if limit is reached + if (counter.TotalRequests > rule.Limit) + { + //compute retry after value + var retryAfter = _processor.RetryAfterFrom(counter.Timestamp, rule); + + // log blocked request + LogBlockedRequest(httpContext, identity, counter, rule, downstreamReRoute); + var retrystring = retryAfter.ToString(System.Globalization.CultureInfo.InvariantCulture); // break execution - await ReturnQuotaExceededResponse(context.HttpContext, options, retrystring); + var ds = ReturnQuotaExceededResponse(httpContext, options, retrystring); + httpContext.Items.UpsertDownstreamResponse(ds); // Set Error - context.Errors.Add(new QuotaExceededError(this.GetResponseMessage(options))); - - return; - } - } - - //set X-Rate-Limit headers for the longest period - if (!options.DisableRateLimitHeaders) - { - var headers = _processor.GetRateLimitHeaders(context.HttpContext, identity, options); - context.HttpContext.Response.OnStarting(SetRateLimitHeaders, state: headers); - } - - await _next.Invoke(context); - } - - public virtual ClientRequestIdentity SetIdentity(HttpContext httpContext, RateLimitOptions option) - { - var clientId = "client"; - if (httpContext.Request.Headers.Keys.Contains(option.ClientIdHeader)) - { - clientId = httpContext.Request.Headers[option.ClientIdHeader].First(); - } - - return new ClientRequestIdentity( - clientId, - httpContext.Request.Path.ToString().ToLowerInvariant(), - httpContext.Request.Method.ToLowerInvariant() - ); - } - - public bool IsWhitelisted(ClientRequestIdentity requestIdentity, RateLimitOptions option) - { - if (option.ClientWhitelist.Contains(requestIdentity.ClientId)) - { - return true; - } - - return false; - } - - public virtual void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule, DownstreamReRoute downstreamReRoute) - { - Logger.LogInformation( - $"Request {identity.HttpVerb}:{identity.Path} from ClientId {identity.ClientId} has been blocked, quota {rule.Limit}/{rule.Period} exceeded by {counter.TotalRequests}. Blocked by rule { downstreamReRoute.UpstreamPathTemplate.OriginalValue }, TraceIdentifier {httpContext.TraceIdentifier}."); + httpContext.Items.SetError(new QuotaExceededError(this.GetResponseMessage(options), options.HttpStatusCode)); + + return; + } + } + + //set X-Rate-Limit headers for the longest period + if (!options.DisableRateLimitHeaders) + { + var headers = _processor.GetRateLimitHeaders(httpContext, identity, options); + httpContext.Response.OnStarting(SetRateLimitHeaders, state: headers); + } + + await _next.Invoke(httpContext); } - public virtual Task ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitOptions option, string retryAfter) - { - var message = this.GetResponseMessage(option); - - if (!option.DisableRateLimitHeaders) - { - httpContext.Response.Headers["Retry-After"] = retryAfter; - } - - httpContext.Response.StatusCode = option.HttpStatusCode; - return httpContext.Response.WriteAsync(message); - } - - private string GetResponseMessage(RateLimitOptions option) - { - var message = string.IsNullOrEmpty(option.QuotaExceededMessage) - ? $"API calls quota exceeded! maximum admitted {option.RateLimitRule.Limit} per {option.RateLimitRule.Period}." - : option.QuotaExceededMessage; - return message; - } - - private Task SetRateLimitHeaders(object rateLimitHeaders) - { - var headers = (RateLimitHeaders)rateLimitHeaders; - - headers.Context.Response.Headers["X-Rate-Limit-Limit"] = headers.Limit; - headers.Context.Response.Headers["X-Rate-Limit-Remaining"] = headers.Remaining; - headers.Context.Response.Headers["X-Rate-Limit-Reset"] = headers.Reset; - - return Task.CompletedTask; - } - } + public virtual ClientRequestIdentity SetIdentity(HttpContext httpContext, RateLimitOptions option) + { + var clientId = "client"; + if (httpContext.Request.Headers.Keys.Contains(option.ClientIdHeader)) + { + clientId = httpContext.Request.Headers[option.ClientIdHeader].First(); + } + + return new ClientRequestIdentity( + clientId, + httpContext.Request.Path.ToString().ToLowerInvariant(), + httpContext.Request.Method.ToLowerInvariant() + ); + } + + public bool IsWhitelisted(ClientRequestIdentity requestIdentity, RateLimitOptions option) + { + if (option.ClientWhitelist.Contains(requestIdentity.ClientId)) + { + return true; + } + + return false; + } + + public virtual void LogBlockedRequest(HttpContext httpContext, ClientRequestIdentity identity, RateLimitCounter counter, RateLimitRule rule, DownstreamReRoute downstreamReRoute) + { + Logger.LogInformation( + $"Request {identity.HttpVerb}:{identity.Path} from ClientId {identity.ClientId} has been blocked, quota {rule.Limit}/{rule.Period} exceeded by {counter.TotalRequests}. Blocked by rule { downstreamReRoute.UpstreamPathTemplate.OriginalValue }, TraceIdentifier {httpContext.TraceIdentifier}."); + } + + public virtual DownstreamResponse ReturnQuotaExceededResponse(HttpContext httpContext, RateLimitOptions option, string retryAfter) + { + var message = GetResponseMessage(option); + + var http = new HttpResponseMessage((HttpStatusCode)option.HttpStatusCode); + + http.Content = new StringContent(message); + + if (!option.DisableRateLimitHeaders) + { + http.Headers.TryAddWithoutValidation("Retry-After", retryAfter); + } + + return new DownstreamResponse(http); + } + + private string GetResponseMessage(RateLimitOptions option) + { + var message = string.IsNullOrEmpty(option.QuotaExceededMessage) + ? $"API calls quota exceeded! maximum admitted {option.RateLimitRule.Limit} per {option.RateLimitRule.Period}." + : option.QuotaExceededMessage; + return message; + } + + private Task SetRateLimitHeaders(object rateLimitHeaders) + { + var headers = (RateLimitHeaders)rateLimitHeaders; + + headers.Context.Response.Headers["X-Rate-Limit-Limit"] = headers.Limit; + headers.Context.Response.Headers["X-Rate-Limit-Remaining"] = headers.Remaining; + headers.Context.Response.Headers["X-Rate-Limit-Reset"] = headers.Reset; + + return Task.CompletedTask; + } + } } diff --git a/src/Ocelot/RateLimit/Middleware/RateLimitMiddlewareExtensions.cs b/src/Ocelot/RateLimit/Middleware/RateLimitMiddlewareExtensions.cs index 3d686196..c2dd5e79 100644 --- a/src/Ocelot/RateLimit/Middleware/RateLimitMiddlewareExtensions.cs +++ b/src/Ocelot/RateLimit/Middleware/RateLimitMiddlewareExtensions.cs @@ -1,12 +1,12 @@ -using Ocelot.Middleware.Pipeline; - -namespace Ocelot.RateLimit.Middleware -{ - public static class RateLimitMiddlewareExtensions - { - public static IOcelotPipelineBuilder UseRateLimiting(this IOcelotPipelineBuilder builder) - { - return builder.UseMiddleware(); - } - } +namespace Ocelot.RateLimit.Middleware +{ + using Microsoft.AspNetCore.Builder; + + public static class RateLimitMiddlewareExtensions + { + public static IApplicationBuilder UseRateLimiting(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } } diff --git a/src/Ocelot/RateLimit/QuotaExceededError.cs b/src/Ocelot/RateLimit/QuotaExceededError.cs index f025161c..9c98dc5a 100644 --- a/src/Ocelot/RateLimit/QuotaExceededError.cs +++ b/src/Ocelot/RateLimit/QuotaExceededError.cs @@ -4,8 +4,8 @@ namespace Ocelot.RateLimit { public class QuotaExceededError : Error { - public QuotaExceededError(string message) - : base(message, OcelotErrorCode.QuotaExceededError) + public QuotaExceededError(string message, int httpStatusCode) + : base(message, OcelotErrorCode.QuotaExceededError, httpStatusCode) { } } diff --git a/src/Ocelot/Request/Mapper/UnmappableRequestError.cs b/src/Ocelot/Request/Mapper/UnmappableRequestError.cs index fa5a602a..1902979f 100644 --- a/src/Ocelot/Request/Mapper/UnmappableRequestError.cs +++ b/src/Ocelot/Request/Mapper/UnmappableRequestError.cs @@ -1,12 +1,12 @@ -namespace Ocelot.Request.Mapper -{ - using Ocelot.Errors; - using System; - - public class UnmappableRequestError : Error - { - public UnmappableRequestError(Exception exception) : base($"Error when parsing incoming request, exception: {exception}", OcelotErrorCode.UnmappableRequestError) - { - } - } +namespace Ocelot.Request.Mapper +{ + using Ocelot.Errors; + using System; + + public class UnmappableRequestError : Error + { + public UnmappableRequestError(Exception exception) : base($"Error when parsing incoming request, exception: {exception}", OcelotErrorCode.UnmappableRequestError, 404) + { + } + } } diff --git a/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs b/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs index 83a2ecb9..9008c85c 100644 --- a/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs +++ b/src/Ocelot/Request/Middleware/DownstreamRequestInitialiserMiddleware.cs @@ -4,14 +4,16 @@ namespace Ocelot.Request.Middleware using Ocelot.Middleware; using Ocelot.Request.Creator; using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Ocelot.DownstreamRouteFinder.Middleware; public class DownstreamRequestInitialiserMiddleware : OcelotMiddleware { - private readonly OcelotRequestDelegate _next; + private readonly RequestDelegate _next; private readonly Mapper.IRequestMapper _requestMapper; private readonly IDownstreamRequestCreator _creator; - public DownstreamRequestInitialiserMiddleware(OcelotRequestDelegate next, + public DownstreamRequestInitialiserMiddleware(RequestDelegate next, IOcelotLoggerFactory loggerFactory, Mapper.IRequestMapper requestMapper, IDownstreamRequestCreator creator) @@ -22,19 +24,23 @@ namespace Ocelot.Request.Middleware _creator = creator; } - public async Task Invoke(DownstreamContext context) + public async Task Invoke(HttpContext httpContext) { - var downstreamRequest = await _requestMapper.Map(context.HttpContext.Request, context.DownstreamReRoute); + var downstreamReRoute = httpContext.Items.DownstreamReRoute(); - if (downstreamRequest.IsError) + var httpRequestMessage = await _requestMapper.Map(httpContext.Request, downstreamReRoute); + + if (httpRequestMessage.IsError) { - SetPipelineError(context, downstreamRequest.Errors); + httpContext.Items.UpsertErrors(httpRequestMessage.Errors); return; } - context.DownstreamRequest = _creator.Create(downstreamRequest.Data); + var downstreamRequest = _creator.Create(httpRequestMessage.Data); - await _next.Invoke(context); + httpContext.Items.UpsertDownstreamRequest(downstreamRequest); + + await _next.Invoke(httpContext); } } } diff --git a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs index a2931cbb..4150b8cf 100644 --- a/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs +++ b/src/Ocelot/Request/Middleware/HttpRequestBuilderMiddlewareExtensions.cs @@ -1,12 +1,12 @@ -using Ocelot.Middleware.Pipeline; - -namespace Ocelot.Request.Middleware -{ - public static class HttpRequestBuilderMiddlewareExtensions - { - public static IOcelotPipelineBuilder UseDownstreamRequestInitialiser(this IOcelotPipelineBuilder builder) - { - return builder.UseMiddleware(); - } - } +namespace Ocelot.Request.Middleware +{ + using Microsoft.AspNetCore.Builder; + + public static class HttpRequestBuilderMiddlewareExtensions + { + public static IApplicationBuilder UseDownstreamRequestInitialiser(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } } diff --git a/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs b/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs index 4c802a02..fa7e0db0 100644 --- a/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs +++ b/src/Ocelot/RequestId/Middleware/ReRouteRequestIdMiddleware.cs @@ -1,78 +1,83 @@ -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Middleware; -using Ocelot.Request.Middleware; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http.Headers; -using System.Threading.Tasks; - -namespace Ocelot.RequestId.Middleware -{ - public class ReRouteRequestIdMiddleware : OcelotMiddleware - { - private readonly OcelotRequestDelegate _next; - private readonly IRequestScopedDataRepository _requestScopedDataRepository; - - public ReRouteRequestIdMiddleware(OcelotRequestDelegate next, +namespace Ocelot.RequestId.Middleware +{ + using Microsoft.AspNetCore.Http; + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.Infrastructure.RequestData; + using Ocelot.Logging; + using Ocelot.Middleware; + using Ocelot.Request.Middleware; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http.Headers; + using System.Threading.Tasks; + + public class ReRouteRequestIdMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IRequestScopedDataRepository _requestScopedDataRepository; + public ReRouteRequestIdMiddleware(RequestDelegate next, IOcelotLoggerFactory loggerFactory, - IRequestScopedDataRepository requestScopedDataRepository) - : base(loggerFactory.CreateLogger()) - { - _next = next; - _requestScopedDataRepository = requestScopedDataRepository; - } - - public async Task Invoke(DownstreamContext context) - { - SetOcelotRequestId(context); - await _next.Invoke(context); - } - - private void SetOcelotRequestId(DownstreamContext context) - { - var key = context.DownstreamReRoute.RequestIdKey ?? DefaultRequestIdKey.Value; - - if (context.HttpContext.Request.Headers.TryGetValue(key, out var upstreamRequestIds)) - { - context.HttpContext.TraceIdentifier = upstreamRequestIds.First(); - - var previousRequestId = _requestScopedDataRepository.Get("RequestId"); - if (!previousRequestId.IsError && !string.IsNullOrEmpty(previousRequestId.Data) && previousRequestId.Data != context.HttpContext.TraceIdentifier) - { - _requestScopedDataRepository.Add("PreviousRequestId", previousRequestId.Data); - _requestScopedDataRepository.Update("RequestId", context.HttpContext.TraceIdentifier); - } - else - { - _requestScopedDataRepository.Add("RequestId", context.HttpContext.TraceIdentifier); - } - } - - var requestId = new RequestId(context.DownstreamReRoute.RequestIdKey, context.HttpContext.TraceIdentifier); - - if (ShouldAddRequestId(requestId, context.DownstreamRequest.Headers)) - { - AddRequestIdHeader(requestId, context.DownstreamRequest); - } - } - - private bool ShouldAddRequestId(RequestId requestId, HttpRequestHeaders headers) - { - return !string.IsNullOrEmpty(requestId?.RequestIdKey) - && !string.IsNullOrEmpty(requestId.RequestIdValue) - && !RequestIdInHeaders(requestId, headers); - } - - private bool RequestIdInHeaders(RequestId requestId, HttpRequestHeaders headers) - { - IEnumerable value; - return headers.TryGetValues(requestId.RequestIdKey, out value); - } - - private void AddRequestIdHeader(RequestId requestId, DownstreamRequest httpRequestMessage) - { - httpRequestMessage.Headers.Add(requestId.RequestIdKey, requestId.RequestIdValue); - } - } + IRequestScopedDataRepository requestScopedDataRepository) + : base(loggerFactory.CreateLogger()) + { + _next = next; + _requestScopedDataRepository = requestScopedDataRepository; + } + + public async Task Invoke(HttpContext httpContext) + { + SetOcelotRequestId(httpContext); + await _next.Invoke(httpContext); + } + + private void SetOcelotRequestId(HttpContext httpContext) + { + var downstreamReRoute = httpContext.Items.DownstreamReRoute(); + + var key = downstreamReRoute.RequestIdKey ?? DefaultRequestIdKey.Value; + + if (httpContext.Request.Headers.TryGetValue(key, out var upstreamRequestIds)) + { + httpContext.TraceIdentifier = upstreamRequestIds.First(); + + var previousRequestId = _requestScopedDataRepository.Get("RequestId"); + if (!previousRequestId.IsError && !string.IsNullOrEmpty(previousRequestId.Data) && previousRequestId.Data != httpContext.TraceIdentifier) + { + _requestScopedDataRepository.Add("PreviousRequestId", previousRequestId.Data); + _requestScopedDataRepository.Update("RequestId", httpContext.TraceIdentifier); + } + else + { + _requestScopedDataRepository.Add("RequestId", httpContext.TraceIdentifier); + } + } + + var requestId = new RequestId(downstreamReRoute.RequestIdKey, httpContext.TraceIdentifier); + + var downstreamRequest = httpContext.Items.DownstreamRequest(); + + if (ShouldAddRequestId(requestId, downstreamRequest.Headers)) + { + AddRequestIdHeader(requestId, downstreamRequest); + } + } + + private bool ShouldAddRequestId(RequestId requestId, HttpRequestHeaders headers) + { + return !string.IsNullOrEmpty(requestId?.RequestIdKey) + && !string.IsNullOrEmpty(requestId.RequestIdValue) + && !RequestIdInHeaders(requestId, headers); + } + + private bool RequestIdInHeaders(RequestId requestId, HttpRequestHeaders headers) + { + IEnumerable value; + return headers.TryGetValues(requestId.RequestIdKey, out value); + } + + private void AddRequestIdHeader(RequestId requestId, DownstreamRequest httpRequestMessage) + { + httpRequestMessage.Headers.Add(requestId.RequestIdKey, requestId.RequestIdValue); + } + } } diff --git a/src/Ocelot/RequestId/Middleware/RequestIdMiddlewareExtensions.cs b/src/Ocelot/RequestId/Middleware/RequestIdMiddlewareExtensions.cs index ce4e892d..8f872da5 100644 --- a/src/Ocelot/RequestId/Middleware/RequestIdMiddlewareExtensions.cs +++ b/src/Ocelot/RequestId/Middleware/RequestIdMiddlewareExtensions.cs @@ -1,13 +1,12 @@ -using Microsoft.AspNetCore.Builder; -using Ocelot.Middleware.Pipeline; - -namespace Ocelot.RequestId.Middleware -{ - public static class RequestIdMiddlewareExtensions - { - public static IOcelotPipelineBuilder UseRequestIdMiddleware(this IOcelotPipelineBuilder builder) - { - return builder.UseMiddleware(); - } - } +namespace Ocelot.RequestId.Middleware +{ + using Microsoft.AspNetCore.Builder; + + public static class RequestIdMiddlewareExtensions + { + public static IApplicationBuilder UseRequestIdMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } } diff --git a/src/Ocelot/Requester/ConnectionToDownstreamServiceError.cs b/src/Ocelot/Requester/ConnectionToDownstreamServiceError.cs index 6c03863c..78f6c3c2 100644 --- a/src/Ocelot/Requester/ConnectionToDownstreamServiceError.cs +++ b/src/Ocelot/Requester/ConnectionToDownstreamServiceError.cs @@ -6,7 +6,7 @@ namespace Ocelot.Requester public class ConnectionToDownstreamServiceError : Error { public ConnectionToDownstreamServiceError(Exception exception) - : base($"Error connecting to downstream service, exception: {exception}", OcelotErrorCode.ConnectionToDownstreamServiceError) + : base($"Error connecting to downstream service, exception: {exception}", OcelotErrorCode.ConnectionToDownstreamServiceError, 502) { } } diff --git a/src/Ocelot/Requester/HttpClientBuilder.cs b/src/Ocelot/Requester/HttpClientBuilder.cs index e7d45857..0c45f836 100644 --- a/src/Ocelot/Requester/HttpClientBuilder.cs +++ b/src/Ocelot/Requester/HttpClientBuilder.cs @@ -1,13 +1,12 @@ -using Ocelot.Configuration; -using Ocelot.Logging; -using Ocelot.Middleware; -using System; -using System.Linq; -using System.Net; -using System.Net.Http; - -namespace Ocelot.Requester +namespace Ocelot.Requester { + using Ocelot.Configuration; + using Ocelot.Logging; + using System; + using System.Linq; + using System.Net; + using System.Net.Http; + public class HttpClientBuilder : IHttpClientBuilder { private readonly IDelegatingHandlerHandlerFactory _factory; @@ -32,9 +31,9 @@ namespace Ocelot.Requester _defaultTimeout = TimeSpan.FromSeconds(90); } - public IHttpClient Create(DownstreamContext context) + public IHttpClient Create(DownstreamReRoute downstreamReRoute) { - _cacheKey = context.DownstreamReRoute; + _cacheKey = downstreamReRoute; var httpClient = _cacheHandlers.Get(_cacheKey); @@ -44,21 +43,21 @@ namespace Ocelot.Requester return httpClient; } - var handler = CreateHandler(context); + var handler = CreateHandler(downstreamReRoute); - if (context.DownstreamReRoute.DangerousAcceptAnyServerCertificateValidator) + if (downstreamReRoute.DangerousAcceptAnyServerCertificateValidator) { handler.ServerCertificateCustomValidationCallback = (request, certificate, chain, errors) => true; _logger - .LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamReRoute, UpstreamPathTemplate: {context.DownstreamReRoute.UpstreamPathTemplate}, DownstreamPathTemplate: {context.DownstreamReRoute.DownstreamPathTemplate}"); + .LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamReRoute, UpstreamPathTemplate: {downstreamReRoute.UpstreamPathTemplate}, DownstreamPathTemplate: {downstreamReRoute.DownstreamPathTemplate}"); } - var timeout = context.DownstreamReRoute.QosOptions.TimeoutValue == 0 + var timeout = downstreamReRoute.QosOptions.TimeoutValue == 0 ? _defaultTimeout - : TimeSpan.FromMilliseconds(context.DownstreamReRoute.QosOptions.TimeoutValue); + : TimeSpan.FromMilliseconds(downstreamReRoute.QosOptions.TimeoutValue); - _httpClient = new HttpClient(CreateHttpMessageHandler(handler, context.DownstreamReRoute)) + _httpClient = new HttpClient(CreateHttpMessageHandler(handler, downstreamReRoute)) { Timeout = timeout }; @@ -68,43 +67,35 @@ namespace Ocelot.Requester return _client; } - private HttpClientHandler CreateHandler(DownstreamContext context) + private HttpClientHandler CreateHandler(DownstreamReRoute downstreamReRoute) { // Dont' create the CookieContainer if UseCookies is not set or the HttpClient will complain // under .Net Full Framework - bool useCookies = context.DownstreamReRoute.HttpHandlerOptions.UseCookieContainer; + var useCookies = downstreamReRoute.HttpHandlerOptions.UseCookieContainer; - if (useCookies) - { - return UseCookiesHandler(context); - } - else - { - return UseNonCookiesHandler(context); - } + return useCookies ? UseCookiesHandler(downstreamReRoute) : UseNonCookiesHandler(downstreamReRoute); } - private HttpClientHandler UseNonCookiesHandler(DownstreamContext context) + private HttpClientHandler UseNonCookiesHandler(DownstreamReRoute downstreamReRoute) { return new HttpClientHandler { - AllowAutoRedirect = context.DownstreamReRoute.HttpHandlerOptions.AllowAutoRedirect, - UseCookies = context.DownstreamReRoute.HttpHandlerOptions.UseCookieContainer, - UseProxy = context.DownstreamReRoute.HttpHandlerOptions.UseProxy, - MaxConnectionsPerServer = context.DownstreamReRoute.HttpHandlerOptions.MaxConnectionsPerServer - + AllowAutoRedirect = downstreamReRoute.HttpHandlerOptions.AllowAutoRedirect, + UseCookies = downstreamReRoute.HttpHandlerOptions.UseCookieContainer, + UseProxy = downstreamReRoute.HttpHandlerOptions.UseProxy, + MaxConnectionsPerServer = downstreamReRoute.HttpHandlerOptions.MaxConnectionsPerServer, }; } - private HttpClientHandler UseCookiesHandler(DownstreamContext context) + private HttpClientHandler UseCookiesHandler(DownstreamReRoute downstreamReRoute) { return new HttpClientHandler { - AllowAutoRedirect = context.DownstreamReRoute.HttpHandlerOptions.AllowAutoRedirect, - UseCookies = context.DownstreamReRoute.HttpHandlerOptions.UseCookieContainer, - UseProxy = context.DownstreamReRoute.HttpHandlerOptions.UseProxy, - MaxConnectionsPerServer = context.DownstreamReRoute.HttpHandlerOptions.MaxConnectionsPerServer, - CookieContainer = new CookieContainer() + AllowAutoRedirect = downstreamReRoute.HttpHandlerOptions.AllowAutoRedirect, + UseCookies = downstreamReRoute.HttpHandlerOptions.UseCookieContainer, + UseProxy = downstreamReRoute.HttpHandlerOptions.UseProxy, + MaxConnectionsPerServer = downstreamReRoute.HttpHandlerOptions.MaxConnectionsPerServer, + CookieContainer = new CookieContainer(), }; } diff --git a/src/Ocelot/Requester/HttpClientHttpRequester.cs b/src/Ocelot/Requester/HttpClientHttpRequester.cs index eecf37db..f3fb87a2 100644 --- a/src/Ocelot/Requester/HttpClientHttpRequester.cs +++ b/src/Ocelot/Requester/HttpClientHttpRequester.cs @@ -1,12 +1,15 @@ -using Ocelot.Logging; -using Ocelot.Middleware; -using Ocelot.Responses; -using System; -using System.Net.Http; -using System.Threading.Tasks; - namespace Ocelot.Requester { + using Ocelot.Configuration; + using Microsoft.AspNetCore.Http; + using Ocelot.Logging; + using Ocelot.Middleware; + using Ocelot.Responses; + using System; + using System.Net.Http; + using System.Threading.Tasks; + using Ocelot.DownstreamRouteFinder.Middleware; + public class HttpClientHttpRequester : IHttpRequester { private readonly IHttpClientCache _cacheHandlers; @@ -25,15 +28,19 @@ namespace Ocelot.Requester _mapper = mapper; } - public async Task> GetResponse(DownstreamContext context) + public async Task> GetResponse(HttpContext httpContext) { var builder = new HttpClientBuilder(_factory, _cacheHandlers, _logger); - var httpClient = builder.Create(context); + var downstreamReRoute = httpContext.Items.DownstreamReRoute(); + + var downstreamRequest = httpContext.Items.DownstreamRequest(); + + var httpClient = builder.Create(downstreamReRoute); try { - var response = await httpClient.SendAsync(context.DownstreamRequest.ToHttpRequestMessage(), context.HttpContext.RequestAborted); + var response = await httpClient.SendAsync(downstreamRequest.ToHttpRequestMessage(), httpContext.RequestAborted); return new OkResponse(response); } catch (Exception exception) diff --git a/src/Ocelot/Requester/IHttpClientBuilder.cs b/src/Ocelot/Requester/IHttpClientBuilder.cs index 40bb2949..c9bc6b98 100644 --- a/src/Ocelot/Requester/IHttpClientBuilder.cs +++ b/src/Ocelot/Requester/IHttpClientBuilder.cs @@ -1,10 +1,10 @@ -using Ocelot.Middleware; - -namespace Ocelot.Requester +namespace Ocelot.Requester { + using Ocelot.Configuration; + public interface IHttpClientBuilder { - IHttpClient Create(DownstreamContext request); + IHttpClient Create(DownstreamReRoute downstreamReRoute); void Save(); } diff --git a/src/Ocelot/Requester/IHttpRequester.cs b/src/Ocelot/Requester/IHttpRequester.cs index 18f5ed78..87d4bd58 100644 --- a/src/Ocelot/Requester/IHttpRequester.cs +++ b/src/Ocelot/Requester/IHttpRequester.cs @@ -1,12 +1,12 @@ -using Ocelot.Middleware; -using Ocelot.Responses; -using System.Net.Http; -using System.Threading.Tasks; - -namespace Ocelot.Requester -{ - public interface IHttpRequester - { - Task> GetResponse(DownstreamContext context); - } +namespace Ocelot.Requester +{ + using Microsoft.AspNetCore.Http; + using Ocelot.Responses; + using System.Net.Http; + using System.Threading.Tasks; + + public interface IHttpRequester + { + Task> GetResponse(HttpContext httpContext); + } } diff --git a/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs b/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs index a48aa84d..aa49cadb 100644 --- a/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs +++ b/src/Ocelot/Requester/Middleware/HttpRequesterMiddleware.cs @@ -1,59 +1,63 @@ -using System.Net; -using System.Net.Http; -using Ocelot.Logging; -using Ocelot.Middleware; -using System.Threading.Tasks; -using Ocelot.Responses; - -namespace Ocelot.Requester.Middleware -{ - public class HttpRequesterMiddleware : OcelotMiddleware - { - private readonly OcelotRequestDelegate _next; - private readonly IHttpRequester _requester; - - public HttpRequesterMiddleware(OcelotRequestDelegate next, - IOcelotLoggerFactory loggerFactory, - IHttpRequester requester) - : base(loggerFactory.CreateLogger()) - { - _next = next; - _requester = requester; - } - - public async Task Invoke(DownstreamContext context) - { - var response = await _requester.GetResponse(context); - - CreateLogBasedOnResponse(response); - - if (response.IsError) - { - Logger.LogDebug("IHttpRequester returned an error, setting pipeline error"); - - SetPipelineError(context, response.Errors); - return; - } - - Logger.LogDebug("setting http response message"); - - context.DownstreamResponse = new DownstreamResponse(response.Data); - - await _next.Invoke(context); - } - - private void CreateLogBasedOnResponse(Response response) - { - if (response.Data?.StatusCode <= HttpStatusCode.BadRequest) - { - Logger.LogInformation( - $"{(int)response.Data.StatusCode} ({response.Data.ReasonPhrase}) status code, request uri: {response.Data.RequestMessage?.RequestUri}"); +namespace Ocelot.Requester.Middleware +{ + using Microsoft.AspNetCore.Http; + using System.Net; + using System.Net.Http; + using Ocelot.Logging; + using Ocelot.Middleware; + using System.Threading.Tasks; + using Ocelot.Responses; + using Ocelot.DownstreamRouteFinder.Middleware; + + public class HttpRequesterMiddleware : OcelotMiddleware + { + private readonly RequestDelegate _next; + private readonly IHttpRequester _requester; + + public HttpRequesterMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IHttpRequester requester) + : base(loggerFactory.CreateLogger()) + { + _next = next; + _requester = requester; + } + + public async Task Invoke(HttpContext httpContext) + { + var downstreamReRoute = httpContext.Items.DownstreamReRoute(); + + var response = await _requester.GetResponse(httpContext); + + CreateLogBasedOnResponse(response); + + if (response.IsError) + { + Logger.LogDebug("IHttpRequester returned an error, setting pipeline error"); + + httpContext.Items.UpsertErrors(response.Errors); + return; + } + + Logger.LogDebug("setting http response message"); + + httpContext.Items.UpsertDownstreamResponse(new DownstreamResponse(response.Data)); + + await _next.Invoke(httpContext); + } + + private void CreateLogBasedOnResponse(Response response) + { + if (response.Data?.StatusCode <= HttpStatusCode.BadRequest) + { + Logger.LogInformation( + $"{(int)response.Data.StatusCode} ({response.Data.ReasonPhrase}) status code, request uri: {response.Data.RequestMessage?.RequestUri}"); } - else if (response.Data?.StatusCode >= HttpStatusCode.BadRequest) - { - Logger.LogWarning( - $"{(int) response.Data.StatusCode} ({response.Data.ReasonPhrase}) status code, request uri: {response.Data.RequestMessage?.RequestUri}"); - } - } - } + else if (response.Data?.StatusCode >= HttpStatusCode.BadRequest) + { + Logger.LogWarning( + $"{(int) response.Data.StatusCode} ({response.Data.ReasonPhrase}) status code, request uri: {response.Data.RequestMessage?.RequestUri}"); + } + } + } } diff --git a/src/Ocelot/Requester/Middleware/HttpRequesterMiddlewareExtensions.cs b/src/Ocelot/Requester/Middleware/HttpRequesterMiddlewareExtensions.cs index d92f7909..5a6aa491 100644 --- a/src/Ocelot/Requester/Middleware/HttpRequesterMiddlewareExtensions.cs +++ b/src/Ocelot/Requester/Middleware/HttpRequesterMiddlewareExtensions.cs @@ -1,13 +1,12 @@ -using Microsoft.AspNetCore.Builder; -using Ocelot.Middleware.Pipeline; - -namespace Ocelot.Requester.Middleware -{ - public static class HttpRequesterMiddlewareExtensions - { - public static IOcelotPipelineBuilder UseHttpRequesterMiddleware(this IOcelotPipelineBuilder builder) - { - return builder.UseMiddleware(); - } - } +namespace Ocelot.Requester.Middleware +{ + using Microsoft.AspNetCore.Builder; + + public static class HttpRequesterMiddlewareExtensions + { + public static IApplicationBuilder UseHttpRequesterMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + } } diff --git a/src/Ocelot/Requester/QoS/UnableToFindQoSProviderError.cs b/src/Ocelot/Requester/QoS/UnableToFindQoSProviderError.cs index de0882ac..f84718e6 100644 --- a/src/Ocelot/Requester/QoS/UnableToFindQoSProviderError.cs +++ b/src/Ocelot/Requester/QoS/UnableToFindQoSProviderError.cs @@ -1,12 +1,12 @@ -namespace Ocelot.Requester.QoS -{ - using Ocelot.Errors; +namespace Ocelot.Requester.QoS +{ + using Ocelot.Errors; - public class UnableToFindQoSProviderError : Error - { + public class UnableToFindQoSProviderError : Error + { public UnableToFindQoSProviderError(string message) - : base(message, OcelotErrorCode.UnableToFindQoSProviderError) - { - } - } + : base(message, OcelotErrorCode.UnableToFindQoSProviderError, 404) + { + } + } } diff --git a/src/Ocelot/Requester/RequestCanceledError.cs b/src/Ocelot/Requester/RequestCanceledError.cs index 1944fada..8315794e 100644 --- a/src/Ocelot/Requester/RequestCanceledError.cs +++ b/src/Ocelot/Requester/RequestCanceledError.cs @@ -4,7 +4,11 @@ namespace Ocelot.Requester { public class RequestCanceledError : Error { - public RequestCanceledError(string message) : base(message, OcelotErrorCode.RequestCanceled) + public RequestCanceledError(string message) + // status code refer to + // https://stackoverflow.com/questions/46234679/what-is-the-correct-http-status-code-for-a-cancelled-request?answertab=votes#tab-top + // https://httpstatuses.com/499 + : base(message, OcelotErrorCode.RequestCanceled, 499) { } } diff --git a/src/Ocelot/Requester/UnableToCompleteRequestError.cs b/src/Ocelot/Requester/UnableToCompleteRequestError.cs index 26302039..becfa5a1 100644 --- a/src/Ocelot/Requester/UnableToCompleteRequestError.cs +++ b/src/Ocelot/Requester/UnableToCompleteRequestError.cs @@ -1,13 +1,13 @@ -using Ocelot.Errors; -using System; - -namespace Ocelot.Requester -{ - public class UnableToCompleteRequestError : Error - { +using Ocelot.Errors; +using System; + +namespace Ocelot.Requester +{ + public class UnableToCompleteRequestError : Error + { public UnableToCompleteRequestError(Exception exception) - : base($"Error making http request, exception: {exception}", OcelotErrorCode.UnableToCompleteRequestError) - { - } - } + : base($"Error making http request, exception: {exception}", OcelotErrorCode.UnableToCompleteRequestError, 500) + { + } + } } diff --git a/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs b/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs index 841f788d..39b5e749 100644 --- a/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs +++ b/src/Ocelot/Responder/ErrorsToHttpStatusCodeMapper.cs @@ -1,58 +1,63 @@ -using Ocelot.Errors; -using System.Collections.Generic; -using System.Linq; - -namespace Ocelot.Responder -{ - public class ErrorsToHttpStatusCodeMapper : IErrorsToHttpStatusCodeMapper - { - public int Map(List errors) - { - if (errors.Any(e => e.Code == OcelotErrorCode.UnauthenticatedError)) - { - return 401; - } - - if (errors.Any(e => e.Code == OcelotErrorCode.UnauthorizedError - || e.Code == OcelotErrorCode.ClaimValueNotAuthorisedError - || e.Code == OcelotErrorCode.ScopeNotAuthorisedError - || e.Code == OcelotErrorCode.UserDoesNotHaveClaimError - || e.Code == OcelotErrorCode.CannotFindClaimError)) - { - return 403; - } - - if (errors.Any(e => e.Code == OcelotErrorCode.RequestTimedOutError)) - { - return 503; - } - - if (errors.Any(e => e.Code == OcelotErrorCode.RequestCanceled)) +using Ocelot.Errors; +using System.Collections.Generic; +using System.Linq; + +namespace Ocelot.Responder +{ + public class ErrorsToHttpStatusCodeMapper : IErrorsToHttpStatusCodeMapper + { + public int Map(List errors) + { + if (errors.Any(e => e.Code == OcelotErrorCode.UnauthenticatedError)) { - // status code refer to - // https://stackoverflow.com/questions/46234679/what-is-the-correct-http-status-code-for-a-cancelled-request?answertab=votes#tab-top - // https://httpstatuses.com/499 - return 499; - } - - if (errors.Any(e => e.Code == OcelotErrorCode.UnableToFindDownstreamRouteError)) - { - return 404; + return 401; } - if (errors.Any(e => e.Code == OcelotErrorCode.ConnectionToDownstreamServiceError)) - { - return 502; - } - - if (errors.Any(e => e.Code == OcelotErrorCode.UnableToCompleteRequestError - || e.Code == OcelotErrorCode.CouldNotFindLoadBalancerCreator - || e.Code == OcelotErrorCode.ErrorInvokingLoadBalancerCreator)) - { - return 500; - } - - return 404; - } - } + if (errors.Any(e => e.Code == OcelotErrorCode.UnauthorizedError + || e.Code == OcelotErrorCode.ClaimValueNotAuthorisedError + || e.Code == OcelotErrorCode.ScopeNotAuthorisedError + || e.Code == OcelotErrorCode.UserDoesNotHaveClaimError + || e.Code == OcelotErrorCode.CannotFindClaimError)) + { + return 403; + } + + if (errors.Any(e => e.Code == OcelotErrorCode.QuotaExceededError)) + { + return errors.Single(e => e.Code == OcelotErrorCode.QuotaExceededError).HttpStatusCode; + } + + if (errors.Any(e => e.Code == OcelotErrorCode.RequestTimedOutError)) + { + return 503; + } + + if (errors.Any(e => e.Code == OcelotErrorCode.RequestCanceled)) + { + // status code refer to + // https://stackoverflow.com/questions/46234679/what-is-the-correct-http-status-code-for-a-cancelled-request?answertab=votes#tab-top + // https://httpstatuses.com/499 + return 499; + } + + if (errors.Any(e => e.Code == OcelotErrorCode.UnableToFindDownstreamRouteError)) + { + return 404; + } + + if (errors.Any(e => e.Code == OcelotErrorCode.ConnectionToDownstreamServiceError)) + { + return 502; + } + + if (errors.Any(e => e.Code == OcelotErrorCode.UnableToCompleteRequestError + || e.Code == OcelotErrorCode.CouldNotFindLoadBalancerCreator + || e.Code == OcelotErrorCode.ErrorInvokingLoadBalancerCreator)) + { + return 500; + } + + return 404; + } + } } diff --git a/src/Ocelot/Responder/HttpContextResponder.cs b/src/Ocelot/Responder/HttpContextResponder.cs index 509df689..2f84776a 100644 --- a/src/Ocelot/Responder/HttpContextResponder.cs +++ b/src/Ocelot/Responder/HttpContextResponder.cs @@ -5,6 +5,7 @@ using Ocelot.Headers; using Ocelot.Middleware; using System.Linq; using System.Net; +using System.Net.Http; using System.Threading.Tasks; namespace Ocelot.Responder @@ -66,6 +67,24 @@ namespace Ocelot.Responder SetStatusCode(context, statusCode); } + public async Task SetErrorResponseOnContext(HttpContext context, DownstreamResponse response) + { + var content = await response.Content.ReadAsStreamAsync(); + + if (response.Content.Headers.ContentLength != null) + { + AddHeaderIfDoesntExist(context, new Header("Content-Length", new[] { response.Content.Headers.ContentLength.ToString() })); + } + + using (content) + { + if (context.Response.ContentLength != 0) + { + await content.CopyToAsync(context.Response.Body); + } + } + } + private void SetStatusCode(HttpContext context, int statusCode) { if (!context.Response.HasStarted) diff --git a/src/Ocelot/Responder/IHttpResponder.cs b/src/Ocelot/Responder/IHttpResponder.cs index 5a32950a..a42466fd 100644 --- a/src/Ocelot/Responder/IHttpResponder.cs +++ b/src/Ocelot/Responder/IHttpResponder.cs @@ -1,13 +1,15 @@ -using Microsoft.AspNetCore.Http; -using Ocelot.Middleware; -using System.Threading.Tasks; - -namespace Ocelot.Responder -{ +namespace Ocelot.Responder +{ + using Microsoft.AspNetCore.Http; + using Ocelot.Middleware; + using System.Threading.Tasks; + public interface IHttpResponder { Task SetResponseOnHttpContext(HttpContext context, DownstreamResponse response); - void SetErrorResponseOnContext(HttpContext context, int statusCode); + void SetErrorResponseOnContext(HttpContext context, int statusCode); + + Task SetErrorResponseOnContext(HttpContext context, DownstreamResponse response); } } diff --git a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs index 43f01acb..fb070436 100644 --- a/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs +++ b/src/Ocelot/Responder/Middleware/ResponderMiddleware.cs @@ -1,23 +1,25 @@ -using Microsoft.AspNetCore.Http; -using Ocelot.Errors; -using Ocelot.Infrastructure.Extensions; -using Ocelot.Logging; -using Ocelot.Middleware; -using System.Collections.Generic; -using System.Threading.Tasks; - namespace Ocelot.Responder.Middleware { + using Microsoft.AspNetCore.Http; + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.Errors; + using Ocelot.Infrastructure.Extensions; + using Ocelot.Logging; + using Ocelot.Middleware; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + /// /// 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 RequestDelegate _next; private readonly IHttpResponder _responder; private readonly IErrorsToHttpStatusCodeMapper _codeMapper; - public ResponderMiddleware(OcelotRequestDelegate next, + public ResponderMiddleware(RequestDelegate next, IHttpResponder responder, IOcelotLoggerFactory loggerFactory, IErrorsToHttpStatusCodeMapper codeMapper @@ -29,27 +31,39 @@ namespace Ocelot.Responder.Middleware _codeMapper = codeMapper; } - public async Task Invoke(DownstreamContext context) + public async Task Invoke(HttpContext httpContext) { - await _next.Invoke(context); + await _next.Invoke(httpContext); - if (context.IsError) + var errors = httpContext.Items.Errors(); + // todo check errors is ok + if (errors.Count > 0) { - 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}"); + Logger.LogWarning($"{errors.ToErrorString()} errors found in {MiddlewareName}. Setting error response for request path:{httpContext.Request.Path}, request method: {httpContext.Request.Method}"); - SetErrorResponse(context.HttpContext, context.Errors); + SetErrorResponse(httpContext, errors); } else { Logger.LogDebug("no pipeline errors, setting and returning completed response"); - await _responder.SetResponseOnHttpContext(context.HttpContext, context.DownstreamResponse); + + var downstreamResponse = httpContext.Items.DownstreamResponse(); + + await _responder.SetResponseOnHttpContext(httpContext, downstreamResponse); } } private void SetErrorResponse(HttpContext context, List errors) { + //todo - refactor this all teh way down because its shit var statusCode = _codeMapper.Map(errors); _responder.SetErrorResponseOnContext(context, statusCode); + + if (errors.Any(e => e.Code == OcelotErrorCode.QuotaExceededError)) + { + var downstreamResponse = context.Items.DownstreamResponse(); + _responder.SetErrorResponseOnContext(context, downstreamResponse); + } } } } diff --git a/src/Ocelot/Responder/Middleware/ResponderMiddlewareExtensions.cs b/src/Ocelot/Responder/Middleware/ResponderMiddlewareExtensions.cs index 9e890412..3c8620ec 100644 --- a/src/Ocelot/Responder/Middleware/ResponderMiddlewareExtensions.cs +++ b/src/Ocelot/Responder/Middleware/ResponderMiddlewareExtensions.cs @@ -1,11 +1,10 @@ -using Microsoft.AspNetCore.Builder; -using Ocelot.Middleware.Pipeline; - namespace Ocelot.Responder.Middleware { + using Microsoft.AspNetCore.Builder; + public static class ResponderMiddlewareExtensions { - public static IOcelotPipelineBuilder UseResponderMiddleware(this IOcelotPipelineBuilder builder) + public static IApplicationBuilder UseResponderMiddleware(this IApplicationBuilder builder) { return builder.UseMiddleware(); } diff --git a/src/Ocelot/Responses/ResponseGeneric.cs b/src/Ocelot/Responses/ResponseGeneric.cs index 2deeb113..55d5733e 100644 --- a/src/Ocelot/Responses/ResponseGeneric.cs +++ b/src/Ocelot/Responses/ResponseGeneric.cs @@ -1,22 +1,22 @@ -using Ocelot.Errors; -using System.Collections.Generic; - -namespace Ocelot.Responses -{ -#pragma warning disable SA1649 // File name must match first type name - - public abstract class Response : Response -#pragma warning restore SA1649 // File name must match first type name - { - protected Response(T data) - { - Data = data; - } - - protected Response(List errors) : base(errors) - { - } - +using Ocelot.Errors; +using System.Collections.Generic; + +namespace Ocelot.Responses +{ +#pragma warning disable SA1649 // File name must match first type name + + public abstract class Response : Response +#pragma warning restore SA1649 // File name must match first type name + { + protected Response(T data) + { + Data = data; + } + + protected Response(List errors) : base(errors) + { + } + public T Data { get; private set; } - } + } } diff --git a/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs b/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs index 19c75e59..122ecd51 100644 --- a/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs +++ b/src/Ocelot/Security/IPSecurity/IPSecurityPolicy.cs @@ -1,17 +1,17 @@ -using Ocelot.Configuration; -using Ocelot.Middleware; -using Ocelot.Responses; -using System.Net; -using System.Threading.Tasks; - -namespace Ocelot.Security.IPSecurity +namespace Ocelot.Security.IPSecurity { + using Microsoft.AspNetCore.Http; + using Ocelot.Configuration; + using Ocelot.Middleware; + using Ocelot.Responses; + using System.Threading.Tasks; + public class IPSecurityPolicy : ISecurityPolicy { - public async Task Security(DownstreamContext context) + public async Task Security(DownstreamReRoute downstreamReRoute, HttpContext httpContext) { - IPAddress clientIp = context.HttpContext.Connection.RemoteIpAddress; - SecurityOptions securityOptions = context.DownstreamReRoute.SecurityOptions; + var clientIp = httpContext.Connection.RemoteIpAddress; + var securityOptions = downstreamReRoute.SecurityOptions; if (securityOptions == null) { return new OkResponse(); diff --git a/src/Ocelot/Security/ISecurityPolicy.cs b/src/Ocelot/Security/ISecurityPolicy.cs index c6854610..692a9eab 100644 --- a/src/Ocelot/Security/ISecurityPolicy.cs +++ b/src/Ocelot/Security/ISecurityPolicy.cs @@ -1,11 +1,12 @@ -using Ocelot.Middleware; -using Ocelot.Responses; -using System.Threading.Tasks; - -namespace Ocelot.Security +namespace Ocelot.Security { + using Microsoft.AspNetCore.Http; + using Ocelot.Responses; + using System.Threading.Tasks; + using Ocelot.Configuration; + public interface ISecurityPolicy { - Task Security(DownstreamContext context); + Task Security(DownstreamReRoute downstreamReRoute, HttpContext httpContext); } } diff --git a/src/Ocelot/Security/Middleware/SecurityMiddleware.cs b/src/Ocelot/Security/Middleware/SecurityMiddleware.cs index 657445b8..8c32aa61 100644 --- a/src/Ocelot/Security/Middleware/SecurityMiddleware.cs +++ b/src/Ocelot/Security/Middleware/SecurityMiddleware.cs @@ -1,44 +1,47 @@ -using Ocelot.Logging; -using Ocelot.Middleware; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Ocelot.Security.Middleware +namespace Ocelot.Security.Middleware { + using Ocelot.Logging; + using Ocelot.Middleware; + using System.Collections.Generic; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Http; + using Ocelot.DownstreamRouteFinder.Middleware; + public class SecurityMiddleware : OcelotMiddleware { - private readonly OcelotRequestDelegate _next; - private readonly IOcelotLogger _logger; + private readonly RequestDelegate _next; private readonly IEnumerable _securityPolicies; - public SecurityMiddleware(IOcelotLoggerFactory loggerFactory, - IEnumerable securityPolicies, - OcelotRequestDelegate next) + public SecurityMiddleware(RequestDelegate next, + IOcelotLoggerFactory loggerFactory, + IEnumerable securityPolicies + ) : base(loggerFactory.CreateLogger()) { - _logger = loggerFactory.CreateLogger(); _securityPolicies = securityPolicies; _next = next; } - public async Task Invoke(DownstreamContext context) + public async Task Invoke(HttpContext httpContext) { + var downstreamReRoute = httpContext.Items.DownstreamReRoute(); + if (_securityPolicies != null) { - foreach (var policie in _securityPolicies) + foreach (var policy in _securityPolicies) { - var result = await policie.Security(context); + var result = await policy.Security(downstreamReRoute, httpContext); if (!result.IsError) { continue; } - this.SetPipelineError(context, result.Errors); + httpContext.Items.UpsertErrors(result.Errors); return; } } - await _next.Invoke(context); + await _next.Invoke(httpContext); } } } diff --git a/src/Ocelot/Security/Middleware/SecurityMiddlewareExtensions.cs b/src/Ocelot/Security/Middleware/SecurityMiddlewareExtensions.cs index 6230dae4..0e5e640e 100644 --- a/src/Ocelot/Security/Middleware/SecurityMiddlewareExtensions.cs +++ b/src/Ocelot/Security/Middleware/SecurityMiddlewareExtensions.cs @@ -1,10 +1,10 @@ -using Ocelot.Middleware.Pipeline; - -namespace Ocelot.Security.Middleware +namespace Ocelot.Security.Middleware { + using Microsoft.AspNetCore.Builder; + public static class SecurityMiddlewareExtensions { - public static IOcelotPipelineBuilder UseSecurityMiddleware(this IOcelotPipelineBuilder builder) + public static IApplicationBuilder UseSecurityMiddleware(this IApplicationBuilder builder) { return builder.UseMiddleware(); } diff --git a/src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs b/src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs index 3ac838d2..984db20c 100644 --- a/src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs +++ b/src/Ocelot/ServiceDiscovery/UnableToFindServiceDiscoveryProviderError.cs @@ -4,7 +4,7 @@ namespace Ocelot.ServiceDiscovery public class UnableToFindServiceDiscoveryProviderError : Error { - public UnableToFindServiceDiscoveryProviderError(string message) : base(message, OcelotErrorCode.UnableToFindServiceDiscoveryProviderError) + public UnableToFindServiceDiscoveryProviderError(string message) : base(message, OcelotErrorCode.UnableToFindServiceDiscoveryProviderError, 404) { } } diff --git a/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs b/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs index bc567e36..4546de9f 100644 --- a/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs +++ b/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddleware.cs @@ -2,25 +2,26 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Modified https://github.com/aspnet/Proxy websockets class to use in Ocelot. -using Microsoft.AspNetCore.Http; -using Ocelot.Logging; -using Ocelot.Middleware; -using System; -using System.Linq; -using System.Net.WebSockets; -using System.Threading; -using System.Threading.Tasks; - namespace Ocelot.WebSockets.Middleware { + using Microsoft.AspNetCore.Http; + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.Logging; + using Ocelot.Middleware; + using System; + using System.Linq; + using System.Net.WebSockets; + using System.Threading; + using System.Threading.Tasks; + public class WebSocketsProxyMiddleware : OcelotMiddleware { private static readonly string[] NotForwardedWebSocketHeaders = new[] { "Connection", "Host", "Upgrade", "Sec-WebSocket-Accept", "Sec-WebSocket-Protocol", "Sec-WebSocket-Key", "Sec-WebSocket-Version", "Sec-WebSocket-Extensions" }; private const int DefaultWebSocketBufferSize = 4096; private const int StreamCopyBufferSize = 81920; - private readonly OcelotRequestDelegate _next; + private readonly RequestDelegate _next; - public WebSocketsProxyMiddleware(OcelotRequestDelegate next, + public WebSocketsProxyMiddleware(RequestDelegate next, IOcelotLoggerFactory loggerFactory) : base(loggerFactory.CreateLogger()) { @@ -67,9 +68,10 @@ namespace Ocelot.WebSockets.Middleware } } - public async Task Invoke(DownstreamContext context) + public async Task Invoke(HttpContext httpContext) { - await Proxy(context.HttpContext, context.DownstreamRequest.ToUri()); + var uri = httpContext.Items.DownstreamRequest().ToUri(); + await Proxy(httpContext, uri); } private async Task Proxy(HttpContext context, string serverEndpoint) diff --git a/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddlewareExtensions.cs b/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddlewareExtensions.cs index e973dfc3..9a21c727 100644 --- a/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddlewareExtensions.cs +++ b/src/Ocelot/WebSockets/Middleware/WebSocketsProxyMiddlewareExtensions.cs @@ -1,10 +1,10 @@ -using Ocelot.Middleware.Pipeline; - namespace Ocelot.WebSockets.Middleware { + using Microsoft.AspNetCore.Builder; + public static class WebSocketsProxyMiddlewareExtensions { - public static IOcelotPipelineBuilder UseWebSocketsProxyMiddleware(this IOcelotPipelineBuilder builder) + public static IApplicationBuilder UseWebSocketsProxyMiddleware(this IApplicationBuilder builder) { return builder.UseMiddleware(); } diff --git a/test/Ocelot.AcceptanceTests/AggregateTests.cs b/test/Ocelot.AcceptanceTests/AggregateTests.cs index 8e2f0b81..7a0a2921 100644 --- a/test/Ocelot.AcceptanceTests/AggregateTests.cs +++ b/test/Ocelot.AcceptanceTests/AggregateTests.cs @@ -1,670 +1,669 @@ -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration.File; -using Ocelot.Middleware; -using Ocelot.Middleware.Multiplexer; -using Shouldly; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.AcceptanceTests -{ - public class AggregateTests : IDisposable - { - private readonly Steps _steps; - private string _downstreamPathOne; - private string _downstreamPathTwo; - private readonly ServiceHandler _serviceHandler; - - public AggregateTests() - { - _serviceHandler = new ServiceHandler(); - _steps = new Steps(); - } - - [Fact] - public void should_fix_issue_597() +namespace Ocelot.AcceptanceTests +{ + using Microsoft.AspNetCore.Http; + using Ocelot.Configuration.File; + using Ocelot.Middleware; + using Ocelot.Multiplexer; + using Shouldly; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Threading.Tasks; + using TestStack.BDDfy; + using Xunit; + + public class AggregateTests : IDisposable + { + private readonly Steps _steps; + private string _downstreamPathOne; + private string _downstreamPathTwo; + private readonly ServiceHandler _serviceHandler; + + public AggregateTests() { - var port = RandomPortFinder.GetRandomPort(); - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/api/values?MailId={userid}", - UpstreamPathTemplate = "/key1data/{userid}", - UpstreamHttpMethod = new List {"Get"}, - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port - } - }, - Key = "key1" - }, - new FileReRoute - { - DownstreamPathTemplate = "/api/values?MailId={userid}", - UpstreamPathTemplate = "/key2data/{userid}", - UpstreamHttpMethod = new List {"Get"}, - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port - } - }, - Key = "key2" - }, - new FileReRoute - { - DownstreamPathTemplate = "/api/values?MailId={userid}", - UpstreamPathTemplate = "/key3data/{userid}", - UpstreamHttpMethod = new List {"Get"}, - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port - } - }, - Key = "key3" - }, - new FileReRoute - { - DownstreamPathTemplate = "/api/values?MailId={userid}", - UpstreamPathTemplate = "/key4data/{userid}", - UpstreamHttpMethod = new List {"Get"}, - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port - } - }, - Key = "key4" - }, - }, - Aggregates = new List - { - new FileAggregateReRoute - { - ReRouteKeys = new List{ - "key1", - "key2", - "key3", - "key4" - }, - UpstreamPathTemplate = "/EmpDetail/IN/{userid}" - }, - new FileAggregateReRoute - { - ReRouteKeys = new List{ - "key1", - "key2", - }, - UpstreamPathTemplate = "/EmpDetail/US/{userid}" - } - }, - GlobalConfiguration = new FileGlobalConfiguration - { - RequestIdKey = "CorrelationID" - } - }; - - var expected = "{\"key1\":some_data,\"key2\":some_data}"; - - this.Given(x => x.GivenServiceIsRunning($"http://localhost:{port}", 200, "some_data")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/EmpDetail/US/1")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_advanced_aggregate_configs() + _serviceHandler = new ServiceHandler(); + _steps = new Steps(); + } + + [Fact] + public void should_fix_issue_597() + { + var port = RandomPortFinder.GetRandomPort(); + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/api/values?MailId={userid}", + UpstreamPathTemplate = "/key1data/{userid}", + UpstreamHttpMethod = new List {"Get"}, + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + Key = "key1", + }, + new FileReRoute + { + DownstreamPathTemplate = "/api/values?MailId={userid}", + UpstreamPathTemplate = "/key2data/{userid}", + UpstreamHttpMethod = new List {"Get"}, + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + Key = "key2", + }, + new FileReRoute + { + DownstreamPathTemplate = "/api/values?MailId={userid}", + UpstreamPathTemplate = "/key3data/{userid}", + UpstreamHttpMethod = new List {"Get"}, + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + Key = "key3", + }, + new FileReRoute + { + DownstreamPathTemplate = "/api/values?MailId={userid}", + UpstreamPathTemplate = "/key4data/{userid}", + UpstreamHttpMethod = new List {"Get"}, + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port, + }, + }, + Key = "key4", + }, + }, + Aggregates = new List + { + new FileAggregateReRoute + { + ReRouteKeys = new List{ + "key1", + "key2", + "key3", + "key4", + }, + UpstreamPathTemplate = "/EmpDetail/IN/{userid}", + }, + new FileAggregateReRoute + { + ReRouteKeys = new List{ + "key1", + "key2", + }, + UpstreamPathTemplate = "/EmpDetail/US/{userid}", + }, + }, + GlobalConfiguration = new FileGlobalConfiguration + { + RequestIdKey = "CorrelationID", + }, + }; + + var expected = "{\"key1\":some_data,\"key2\":some_data}"; + + this.Given(x => x.GivenServiceIsRunning($"http://localhost:{port}", 200, "some_data")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/EmpDetail/US/1")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_advanced_aggregate_configs() { var port1 = RandomPortFinder.GetRandomPort(); var port2 = RandomPortFinder.GetRandomPort(); - var port3 = RandomPortFinder.GetRandomPort(); - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port1, - } - }, - UpstreamPathTemplate = "/Comments", - UpstreamHttpMethod = new List { "Get" }, - Key = "Comments" - }, - new FileReRoute - { - DownstreamPathTemplate = "/users/{userId}", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port2, - } - }, - UpstreamPathTemplate = "/UserDetails", - UpstreamHttpMethod = new List { "Get" }, - Key = "UserDetails" - }, - new FileReRoute - { - DownstreamPathTemplate = "/posts/{postId}", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port3, - } - }, - UpstreamPathTemplate = "/PostDetails", - UpstreamHttpMethod = new List { "Get" }, - Key = "PostDetails" - } - }, - Aggregates = new List - { - new FileAggregateReRoute - { - UpstreamPathTemplate = "/", - UpstreamHost = "localhost", - ReRouteKeys = new List - { - "Comments", - "UserDetails", - "PostDetails" - }, - ReRouteKeysConfig = new List() - { - new AggregateReRouteConfig(){ReRouteKey = "UserDetails",JsonPath = "$[*].writerId",Parameter = "userId"}, - new AggregateReRouteConfig(){ReRouteKey = "PostDetails",JsonPath = "$[*].postId",Parameter = "postId"} - }, - } - } - }; - - var userDetailsResponseContent = @"{""id"":1,""firstName"":""abolfazl"",""lastName"":""rajabpour""}"; - var postDetailsResponseContent = @"{""id"":1,""title"":""post1""}"; - var commentsResponseContent = @"[{""id"":1,""writerId"":1,""postId"":2,""text"":""text1""},{""id"":2,""writerId"":1,""postId"":2,""text"":""text2""}]"; - - var expected = "{\"Comments\":" + commentsResponseContent + ",\"UserDetails\":" + userDetailsResponseContent + ",\"PostDetails\":" + postDetailsResponseContent + "}"; - - this.Given(x => x.GivenServiceOneIsRunning($"http://localhost:{port1}", "/", 200, commentsResponseContent)) - .Given(x => x.GivenServiceTwoIsRunning($"http://localhost:{port2}", "/users/1", 200, userDetailsResponseContent)) - .Given(x => x.GivenServiceTwoIsRunning($"http://localhost:{port3}", "/posts/2", 200, postDetailsResponseContent)) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_simple_url_user_defined_aggregate() + var port3 = RandomPortFinder.GetRandomPort(); + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port1, + }, + }, + UpstreamPathTemplate = "/Comments", + UpstreamHttpMethod = new List { "Get" }, + Key = "Comments", + }, + new FileReRoute + { + DownstreamPathTemplate = "/users/{userId}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port2, + }, + }, + UpstreamPathTemplate = "/UserDetails", + UpstreamHttpMethod = new List { "Get" }, + Key = "UserDetails", + }, + new FileReRoute + { + DownstreamPathTemplate = "/posts/{postId}", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port3, + }, + }, + UpstreamPathTemplate = "/PostDetails", + UpstreamHttpMethod = new List { "Get" }, + Key = "PostDetails", + }, + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Comments", + "UserDetails", + "PostDetails", + }, + ReRouteKeysConfig = new List() + { + new AggregateReRouteConfig(){ReRouteKey = "UserDetails",JsonPath = "$[*].writerId",Parameter = "userId"}, + new AggregateReRouteConfig(){ReRouteKey = "PostDetails",JsonPath = "$[*].postId",Parameter = "postId"}, + }, + }, + }, + }; + + var userDetailsResponseContent = @"{""id"":1,""firstName"":""abolfazl"",""lastName"":""rajabpour""}"; + var postDetailsResponseContent = @"{""id"":1,""title"":""post1""}"; + var commentsResponseContent = @"[{""id"":1,""writerId"":1,""postId"":2,""text"":""text1""},{""id"":2,""writerId"":1,""postId"":2,""text"":""text2""}]"; + + var expected = "{\"Comments\":" + commentsResponseContent + ",\"UserDetails\":" + userDetailsResponseContent + ",\"PostDetails\":" + postDetailsResponseContent + "}"; + + this.Given(x => x.GivenServiceOneIsRunning($"http://localhost:{port1}", "/", 200, commentsResponseContent)) + .Given(x => x.GivenServiceTwoIsRunning($"http://localhost:{port2}", "/users/1", 200, userDetailsResponseContent)) + .Given(x => x.GivenServiceTwoIsRunning($"http://localhost:{port3}", "/posts/2", 200, postDetailsResponseContent)) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_simple_url_user_defined_aggregate() { var port1 = RandomPortFinder.GetRandomPort(); - var port2 = RandomPortFinder.GetRandomPort(); - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port1, - } - }, - UpstreamPathTemplate = "/laura", - UpstreamHttpMethod = new List { "Get" }, - Key = "Laura" - }, - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port2, - } - }, - UpstreamPathTemplate = "/tom", - UpstreamHttpMethod = new List { "Get" }, - Key = "Tom" - } - }, - Aggregates = new List - { - new FileAggregateReRoute - { - UpstreamPathTemplate = "/", - UpstreamHost = "localhost", - ReRouteKeys = new List - { - "Laura", - "Tom" - }, - Aggregator = "FakeDefinedAggregator" - } - } - }; - - var expected = "Bye from Laura, Bye from Tom"; - - this.Given(x => x.GivenServiceOneIsRunning($"http://localhost:{port1}", "/", 200, "{Hello from Laura}")) - .Given(x => x.GivenServiceTwoIsRunning($"http://localhost:{port2}", "/", 200, "{Hello from Tom}")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunningWithSpecficAggregatorsRegisteredInDi()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) - .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_simple_url() + var port2 = RandomPortFinder.GetRandomPort(); + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port1, + }, + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura", + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port2, + }, + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom", + }, + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Laura", + "Tom", + }, + Aggregator = "FakeDefinedAggregator", + }, + }, + }; + + var expected = "Bye from Laura, Bye from Tom"; + + this.Given(x => x.GivenServiceOneIsRunning($"http://localhost:{port1}", "/", 200, "{Hello from Laura}")) + .Given(x => x.GivenServiceTwoIsRunning($"http://localhost:{port2}", "/", 200, "{Hello from Tom}")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunningWithSpecficAggregatorsRegisteredInDi()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) + .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_simple_url() { var port1 = RandomPortFinder.GetRandomPort(); - var port2 = RandomPortFinder.GetRandomPort(); - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port1, - } - }, - UpstreamPathTemplate = "/laura", - UpstreamHttpMethod = new List { "Get" }, - Key = "Laura" - }, - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port2, - } - }, - UpstreamPathTemplate = "/tom", - UpstreamHttpMethod = new List { "Get" }, - Key = "Tom" - } - }, - Aggregates = new List - { - new FileAggregateReRoute - { - UpstreamPathTemplate = "/", - UpstreamHost = "localhost", - ReRouteKeys = new List - { - "Laura", - "Tom" - } - } - } - }; - - var expected = "{\"Laura\":{Hello from Laura},\"Tom\":{Hello from Tom}}"; - - this.Given(x => x.GivenServiceOneIsRunning($"http://localhost:{port1}", "/", 200, "{Hello from Laura}")) - .Given(x => x.GivenServiceTwoIsRunning($"http://localhost:{port2}", "/", 200, "{Hello from Tom}")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) - .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_simple_url_one_service_404() + var port2 = RandomPortFinder.GetRandomPort(); + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port1, + }, + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura", + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port2, + }, + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom", + }, + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Laura", + "Tom", + }, + }, + }, + }; + + var expected = "{\"Laura\":{Hello from Laura},\"Tom\":{Hello from Tom}}"; + + this.Given(x => x.GivenServiceOneIsRunning($"http://localhost:{port1}", "/", 200, "{Hello from Laura}")) + .Given(x => x.GivenServiceTwoIsRunning($"http://localhost:{port2}", "/", 200, "{Hello from Tom}")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) + .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_simple_url_one_service_404() { var port1 = RandomPortFinder.GetRandomPort(); - var port2 = RandomPortFinder.GetRandomPort(); - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port1, - } - }, - UpstreamPathTemplate = "/laura", - UpstreamHttpMethod = new List { "Get" }, - Key = "Laura" - }, - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port2, - } - }, - UpstreamPathTemplate = "/tom", - UpstreamHttpMethod = new List { "Get" }, - Key = "Tom" - } - }, - Aggregates = new List - { - new FileAggregateReRoute - { - UpstreamPathTemplate = "/", - UpstreamHost = "localhost", - ReRouteKeys = new List - { - "Laura", - "Tom" - } - } - } - }; - - var expected = "{\"Laura\":,\"Tom\":{Hello from Tom}}"; - - this.Given(x => x.GivenServiceOneIsRunning($"http://localhost:{port1}", "/", 404, "")) - .Given(x => x.GivenServiceTwoIsRunning($"http://localhost:{port2}", "/", 200, "{Hello from Tom}")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) - .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) - .BDDfy(); - } - - [Fact] - public void should_return_response_200_with_simple_url_both_service_404() + var port2 = RandomPortFinder.GetRandomPort(); + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port1, + }, + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura", + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port2, + }, + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom", + }, + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Laura", + "Tom", + }, + }, + }, + }; + + var expected = "{\"Laura\":,\"Tom\":{Hello from Tom}}"; + + this.Given(x => x.GivenServiceOneIsRunning($"http://localhost:{port1}", "/", 404, "")) + .Given(x => x.GivenServiceTwoIsRunning($"http://localhost:{port2}", "/", 200, "{Hello from Tom}")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) + .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) + .BDDfy(); + } + + [Fact] + public void should_return_response_200_with_simple_url_both_service_404() { var port1 = RandomPortFinder.GetRandomPort(); - var port2 = RandomPortFinder.GetRandomPort(); - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port1, - } - }, - UpstreamPathTemplate = "/laura", - UpstreamHttpMethod = new List { "Get" }, - Key = "Laura" - }, - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port2, - } - }, - UpstreamPathTemplate = "/tom", - UpstreamHttpMethod = new List { "Get" }, - Key = "Tom" - } - }, - Aggregates = new List - { - new FileAggregateReRoute - { - UpstreamPathTemplate = "/", - UpstreamHost = "localhost", - ReRouteKeys = new List - { - "Laura", - "Tom" - } - } - } - }; - - var expected = "{\"Laura\":,\"Tom\":}"; - - this.Given(x => x.GivenServiceOneIsRunning($"http://localhost:{port1}", "/", 404, "")) - .Given(x => x.GivenServiceTwoIsRunning($"http://localhost:{port2}", "/", 404, "")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) - .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) - .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) - .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) - .BDDfy(); - } - - [Fact] - public void should_be_thread_safe() + var port2 = RandomPortFinder.GetRandomPort(); + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port1, + }, + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura", + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port2, + }, + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom", + }, + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Laura", + "Tom", + }, + }, + }, + }; + + var expected = "{\"Laura\":,\"Tom\":}"; + + this.Given(x => x.GivenServiceOneIsRunning($"http://localhost:{port1}", "/", 404, "")) + .Given(x => x.GivenServiceTwoIsRunning($"http://localhost:{port2}", "/", 404, "")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIGetUrlOnTheApiGateway("/")) + .Then(x => _steps.ThenTheStatusCodeShouldBe(HttpStatusCode.OK)) + .And(x => _steps.ThenTheResponseBodyShouldBe(expected)) + .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) + .BDDfy(); + } + + [Fact] + public void should_be_thread_safe() { var port1 = RandomPortFinder.GetRandomPort(); - var port2 = RandomPortFinder.GetRandomPort(); - var configuration = new FileConfiguration - { - ReRoutes = new List - { - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port1, - } - }, - UpstreamPathTemplate = "/laura", - UpstreamHttpMethod = new List { "Get" }, - Key = "Laura" - }, - new FileReRoute - { - DownstreamPathTemplate = "/", - DownstreamScheme = "http", - DownstreamHostAndPorts = new List - { - new FileHostAndPort - { - Host = "localhost", - Port = port2, - } - }, - UpstreamPathTemplate = "/tom", - UpstreamHttpMethod = new List { "Get" }, - Key = "Tom" - } - }, - Aggregates = new List - { - new FileAggregateReRoute - { - UpstreamPathTemplate = "/", - UpstreamHost = "localhost", - ReRouteKeys = new List - { - "Laura", - "Tom" - } - } - } - }; - - this.Given(x => x.GivenServiceOneIsRunning($"http://localhost:{port1}", "/", 200, "{Hello from Laura}")) - .Given(x => x.GivenServiceTwoIsRunning($"http://localhost:{port2}", "/", 200, "{Hello from Tom}")) - .And(x => _steps.GivenThereIsAConfiguration(configuration)) - .And(x => _steps.GivenOcelotIsRunning()) - .When(x => _steps.WhenIMakeLotsOfDifferentRequestsToTheApiGateway()) - .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) - .BDDfy(); - } - - private void GivenServiceIsRunning(string baseUrl, int statusCode, string responseBody) - { - _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, async context => - { - context.Response.StatusCode = statusCode; - await context.Response.WriteAsync(responseBody); - }); - } - - private void GivenServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) - { - _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, 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); - } - }); - } - - private void GivenServiceTwoIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) - { - _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, 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); - } - }); - } - - internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPathOne, string expectedDownstreamPath) - { - _downstreamPathOne.ShouldBe(expectedDownstreamPathOne); - _downstreamPathTwo.ShouldBe(expectedDownstreamPath); - } - - public void Dispose() - { - _serviceHandler.Dispose(); - _steps.Dispose(); - } - } - - public class FakeDepdendency - { - } - - public class FakeDefinedAggregator : IDefinedAggregator - { - private readonly FakeDepdendency _dep; - - public FakeDefinedAggregator(FakeDepdendency dep) - { - _dep = dep; - } - - public async Task Aggregate(List responses) - { - var one = await responses[0].DownstreamResponse.Content.ReadAsStringAsync(); - var two = await responses[1].DownstreamResponse.Content.ReadAsStringAsync(); - - var merge = $"{one}, {two}"; - merge = merge.Replace("Hello", "Bye").Replace("{", "").Replace("}", ""); - var headers = responses.SelectMany(x => x.DownstreamResponse.Headers).ToList(); - return new DownstreamResponse(new StringContent(merge), HttpStatusCode.OK, headers, "some reason"); - } - } -} + var port2 = RandomPortFinder.GetRandomPort(); + var configuration = new FileConfiguration + { + ReRoutes = new List + { + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port1, + }, + }, + UpstreamPathTemplate = "/laura", + UpstreamHttpMethod = new List { "Get" }, + Key = "Laura", + }, + new FileReRoute + { + DownstreamPathTemplate = "/", + DownstreamScheme = "http", + DownstreamHostAndPorts = new List + { + new FileHostAndPort + { + Host = "localhost", + Port = port2, + }, + }, + UpstreamPathTemplate = "/tom", + UpstreamHttpMethod = new List { "Get" }, + Key = "Tom", + }, + }, + Aggregates = new List + { + new FileAggregateReRoute + { + UpstreamPathTemplate = "/", + UpstreamHost = "localhost", + ReRouteKeys = new List + { + "Laura", + "Tom", + }, + }, + }, + }; + + this.Given(x => x.GivenServiceOneIsRunning($"http://localhost:{port1}", "/", 200, "{Hello from Laura}")) + .Given(x => x.GivenServiceTwoIsRunning($"http://localhost:{port2}", "/", 200, "{Hello from Tom}")) + .And(x => _steps.GivenThereIsAConfiguration(configuration)) + .And(x => _steps.GivenOcelotIsRunning()) + .When(x => _steps.WhenIMakeLotsOfDifferentRequestsToTheApiGateway()) + .And(x => ThenTheDownstreamUrlPathShouldBe("/", "/")) + .BDDfy(); + } + + private void GivenServiceIsRunning(string baseUrl, int statusCode, string responseBody) + { + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, async context => + { + context.Response.StatusCode = statusCode; + await context.Response.WriteAsync(responseBody); + }); + } + + private void GivenServiceOneIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) + { + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, 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); + } + }); + } + + private void GivenServiceTwoIsRunning(string baseUrl, string basePath, int statusCode, string responseBody) + { + _serviceHandler.GivenThereIsAServiceRunningOn(baseUrl, basePath, 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); + } + }); + } + + internal void ThenTheDownstreamUrlPathShouldBe(string expectedDownstreamPathOne, string expectedDownstreamPath) + { + _downstreamPathOne.ShouldBe(expectedDownstreamPathOne); + _downstreamPathTwo.ShouldBe(expectedDownstreamPath); + } + + public void Dispose() + { + _serviceHandler.Dispose(); + _steps.Dispose(); + } + } + + public class FakeDepdendency + { + } + + public class FakeDefinedAggregator : IDefinedAggregator + { + private readonly FakeDepdendency _dep; + + public FakeDefinedAggregator(FakeDepdendency dep) + { + _dep = dep; + } + + public async Task Aggregate(List responses) + { + var one = await responses[0].Items.DownstreamResponse().Content.ReadAsStringAsync(); + var two = await responses[1].Items.DownstreamResponse().Content.ReadAsStringAsync(); + + var merge = $"{one}, {two}"; + merge = merge.Replace("Hello", "Bye").Replace("{", "").Replace("}", ""); + var headers = responses.SelectMany(x => x.Items.DownstreamResponse().Headers).ToList(); + return new DownstreamResponse(new StringContent(merge), HttpStatusCode.OK, headers, "some reason"); + } + } +} diff --git a/test/Ocelot.AcceptanceTests/LoadBalancerTests.cs b/test/Ocelot.AcceptanceTests/LoadBalancerTests.cs index 4d0c663a..5ebf04ac 100644 --- a/test/Ocelot.AcceptanceTests/LoadBalancerTests.cs +++ b/test/Ocelot.AcceptanceTests/LoadBalancerTests.cs @@ -189,7 +189,7 @@ namespace Ocelot.AcceptanceTests _services = services; } - public async Task> Lease(DownstreamContext downstreamContext) + public async Task> Lease(HttpContext httpContext) { var services = await _services(); lock (_lock) @@ -205,7 +205,7 @@ namespace Ocelot.AcceptanceTests } } - public void Release(ServiceHostAndPort hostAndPort) + public void Release(ServiceHostAndPort hostAndPort) { } } diff --git a/test/Ocelot.AcceptanceTests/Steps.cs b/test/Ocelot.AcceptanceTests/Steps.cs index 317b2d1d..2d22bc5a 100644 --- a/test/Ocelot.AcceptanceTests/Steps.cs +++ b/test/Ocelot.AcceptanceTests/Steps.cs @@ -21,7 +21,7 @@ namespace Ocelot.AcceptanceTests using Ocelot.Infrastructure; using Ocelot.Logging; using Ocelot.Middleware; - using Ocelot.Middleware.Multiplexer; + using Ocelot.Multiplexer; using Ocelot.Provider.Consul; using Ocelot.Provider.Eureka; using Ocelot.Provider.Polly; diff --git a/test/Ocelot.Benchmarks/DownstreamRouteFinderMiddlewareBenchmarks.cs b/test/Ocelot.Benchmarks/DownstreamRouteFinderMiddlewareBenchmarks.cs index 55869213..053b0332 100644 --- a/test/Ocelot.Benchmarks/DownstreamRouteFinderMiddlewareBenchmarks.cs +++ b/test/Ocelot.Benchmarks/DownstreamRouteFinderMiddlewareBenchmarks.cs @@ -1,31 +1,30 @@ -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Columns; -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Validators; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Ocelot.Configuration; -using Ocelot.DependencyInjection; -using Ocelot.DownstreamRouteFinder.Finder; -using Ocelot.DownstreamRouteFinder.Middleware; -using Ocelot.Logging; -using Ocelot.Middleware; -using Ocelot.Middleware.Multiplexer; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - namespace Ocelot.Benchmarks { + using BenchmarkDotNet.Attributes; + using BenchmarkDotNet.Columns; + using BenchmarkDotNet.Configs; + using BenchmarkDotNet.Diagnosers; + using BenchmarkDotNet.Validators; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Ocelot.Configuration; + using Ocelot.DependencyInjection; + using Ocelot.DownstreamRouteFinder.Finder; + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.Logging; + using Ocelot.Middleware; + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + [SimpleJob(launchCount: 1, warmupCount: 2, targetCount: 5)] [Config(typeof(DownstreamRouteFinderMiddlewareBenchmarks))] public class DownstreamRouteFinderMiddlewareBenchmarks : ManualConfig { private DownstreamRouteFinderMiddleware _middleware; - private DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; + private RequestDelegate _next; + private HttpContext _httpContext; public DownstreamRouteFinderMiddlewareBenchmarks() { @@ -43,7 +42,6 @@ namespace Ocelot.Benchmarks var services = serviceCollection.BuildServiceProvider(); var loggerFactory = services.GetService(); var drpf = services.GetService(); - var multiplexer = services.GetService(); _next = async context => { @@ -51,22 +49,21 @@ namespace Ocelot.Benchmarks throw new Exception("BOOM"); }; - _middleware = new DownstreamRouteFinderMiddleware(_next, loggerFactory, drpf, multiplexer); + _middleware = new DownstreamRouteFinderMiddleware(_next, loggerFactory, drpf); + var httpContext = new DefaultHttpContext(); httpContext.Request.Path = new PathString("/test"); httpContext.Request.QueryString = new QueryString("?a=b"); httpContext.Request.Headers.Add("Host", "most"); + httpContext.Items.SetIInternalConfiguration(new InternalConfiguration(new List(), null, null, null, null, null, null, null, null)); - _downstreamContext = new DownstreamContext(httpContext) - { - Configuration = new InternalConfiguration(new List(), null, null, null, null, null, null, null, null) - }; + _httpContext = httpContext; } [Benchmark(Baseline = true)] public async Task Baseline() { - await _middleware.Invoke(_downstreamContext); + await _middleware.Invoke(_httpContext); } } } diff --git a/test/Ocelot.Benchmarks/ExceptionHandlerMiddlewareBenchmarks.cs b/test/Ocelot.Benchmarks/ExceptionHandlerMiddlewareBenchmarks.cs index 995efea5..c4392b8a 100644 --- a/test/Ocelot.Benchmarks/ExceptionHandlerMiddlewareBenchmarks.cs +++ b/test/Ocelot.Benchmarks/ExceptionHandlerMiddlewareBenchmarks.cs @@ -1,61 +1,60 @@ -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Columns; -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Validators; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Ocelot.Configuration.Repository; -using Ocelot.DependencyInjection; -using Ocelot.Errors.Middleware; -using Ocelot.Infrastructure.RequestData; -using Ocelot.Logging; -using Ocelot.Middleware; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Ocelot.Benchmarks -{ - [SimpleJob(launchCount: 1, warmupCount: 2, targetCount: 5)] - [Config(typeof(ExceptionHandlerMiddlewareBenchmarks))] - public class ExceptionHandlerMiddlewareBenchmarks : ManualConfig - { - private ExceptionHandlerMiddleware _middleware; - private DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; - - public ExceptionHandlerMiddlewareBenchmarks() - { - Add(StatisticColumn.AllStatistics); - Add(MemoryDiagnoser.Default); - Add(BaselineValidator.FailOnError); - } - - [GlobalSetup] - public void SetUp() - { - var serviceCollection = new ServiceCollection(); - var config = new ConfigurationRoot(new List()); - var builder = new OcelotBuilder(serviceCollection, config); - var services = serviceCollection.BuildServiceProvider(); - var loggerFactory = services.GetService(); - var configRepo = services.GetService(); - var repo = services.GetService(); +namespace Ocelot.Benchmarks +{ + using BenchmarkDotNet.Attributes; + using BenchmarkDotNet.Columns; + using BenchmarkDotNet.Configs; + using BenchmarkDotNet.Diagnosers; + using BenchmarkDotNet.Validators; + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Ocelot.DependencyInjection; + using Ocelot.Errors.Middleware; + using Ocelot.Infrastructure.RequestData; + using Ocelot.Logging; + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + + [SimpleJob(launchCount: 1, warmupCount: 2, targetCount: 5)] + [Config(typeof(ExceptionHandlerMiddlewareBenchmarks))] + public class ExceptionHandlerMiddlewareBenchmarks : ManualConfig + { + private ExceptionHandlerMiddleware _middleware; + private RequestDelegate _next; + private HttpContext _httpContext; + + public ExceptionHandlerMiddlewareBenchmarks() + { + Add(StatisticColumn.AllStatistics); + Add(MemoryDiagnoser.Default); + Add(BaselineValidator.FailOnError); + } + + [GlobalSetup] + public void SetUp() + { + var serviceCollection = new ServiceCollection(); + var config = new ConfigurationRoot(new List()); + var builder = new OcelotBuilder(serviceCollection, config); + var services = serviceCollection.BuildServiceProvider(); + var loggerFactory = services.GetService(); + var repo = services.GetService(); + _next = async context => - { - await Task.CompletedTask; - throw new Exception("BOOM"); - }; - _middleware = new ExceptionHandlerMiddleware(_next, loggerFactory, configRepo, repo); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - } - - [Benchmark(Baseline = true)] - public async Task Baseline() - { - await _middleware.Invoke(_downstreamContext); - } - } + { + await Task.CompletedTask; + throw new Exception("BOOM"); + }; + + _middleware = new ExceptionHandlerMiddleware(_next, loggerFactory, repo); + _httpContext = new DefaultHttpContext(); + } + + [Benchmark(Baseline = true)] + public async Task Baseline() + { + await _middleware.Invoke(_httpContext); + } + } } diff --git a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs index 467966f9..9cdea86c 100644 --- a/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authentication/AuthenticationMiddlewareTests.cs @@ -1,115 +1,119 @@ -using Xunit; - -[assembly: CollectionBehavior(DisableTestParallelization = true)] - -namespace Ocelot.UnitTests.Authentication -{ - using Microsoft.AspNetCore.Http; - using Moq; - using Ocelot.Authentication.Middleware; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using Ocelot.Logging; - using Ocelot.Middleware; - using Shouldly; - using System.Collections.Generic; - using System.IO; - using System.Text; - using System.Threading.Tasks; - using TestStack.BDDfy; +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] + +namespace Ocelot.UnitTests.Authentication +{ + using Microsoft.AspNetCore.Http; + using Moq; + using Ocelot.Authentication.Middleware; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.Logging; + using Ocelot.Middleware; + using Shouldly; + using System.Collections.Generic; + using System.IO; + using System.Text; + using System.Threading.Tasks; + using Ocelot.Infrastructure.RequestData; + using TestStack.BDDfy; using Xunit; + using Ocelot.DownstreamRouteFinder.Middleware; - public class AuthenticationMiddlewareTests - { - private AuthenticationMiddleware _middleware; - private readonly Mock _factory; - private Mock _logger; - private OcelotRequestDelegate _next; - private readonly DownstreamContext _downstreamContext; - - public AuthenticationMiddlewareTests() + public class AuthenticationMiddlewareTests + { + private AuthenticationMiddleware _middleware; + private readonly Mock _factory; + private Mock _logger; + private RequestDelegate _next; + private HttpContext _httpContext; + private Mock _repo; + + public AuthenticationMiddlewareTests() + { + _repo = new Mock(); + _httpContext = new DefaultHttpContext(); + _factory = new Mock(); + _logger = new Mock(); + _factory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + } + + [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(); + } + + [Fact] + public void should_call_next_middleware_if_route_is_using_options_method() + { + this.Given(x => GivenTheDownStreamRouteIs( + new DownstreamReRouteBuilder() + .WithUpstreamHttpMethod(new List { "Options" }) + .WithIsAuthenticated(true) + .Build())) + .And(x => GivenTheRequestIsUsingOptionsMethod()) + .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); + _httpContext.Response.Body = stream; + return Task.CompletedTask; + }; + _middleware = new AuthenticationMiddleware(_next, _factory.Object); + _middleware.Invoke(_httpContext).GetAwaiter().GetResult(); + } + + private void GivenTheTestServerPipelineIsConfigured() + { + _next = (context) => + { + byte[] byteArray = Encoding.ASCII.GetBytes("The user is authenticated"); + var stream = new MemoryStream(byteArray); + _httpContext.Response.Body = stream; + return Task.CompletedTask; + }; + } + + private void GivenTheRequestIsUsingOptionsMethod() + { + _httpContext.Request.Method = "OPTIONS"; + } + + private void ThenTheUserIsAuthenticated() + { + var content = _httpContext.Response.Body.AsString(); + content.ShouldBe("The user is authenticated"); + } + + private void GivenTheDownStreamRouteIs(DownstreamReRoute downstreamRoute) { - _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(); - } - - [Fact] - public void should_call_next_middleware_if_route_is_using_options_method() - { - this.Given(x => GivenTheDownStreamRouteIs( - new DownstreamReRouteBuilder() - .WithUpstreamHttpMethod(new List { "Options" }) - .WithIsAuthenticated(true) - .Build())) - .And(x => GivenTheRequestIsUsingOptionsMethod()) - .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 GivenTheRequestIsUsingOptionsMethod() - { - _downstreamContext.HttpContext.Request.Method = "OPTIONS"; - } - - 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; - } - } - } -} + _httpContext.Items.UpsertDownstreamReRoute(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/Authorization/AuthorisationMiddlewareTests.cs b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs index eeb99049..737b8b2d 100644 --- a/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Authorization/AuthorisationMiddlewareTests.cs @@ -1,88 +1,89 @@ -using Ocelot.Middleware; - -namespace Ocelot.UnitTests.Authorization -{ - using Microsoft.AspNetCore.Http; - using Moq; - using Ocelot.Authorisation; - using Ocelot.Authorisation.Middleware; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using Ocelot.DownstreamRouteFinder.UrlMatcher; - using Ocelot.Logging; - using Ocelot.Responses; - using System.Collections.Generic; - using System.Security.Claims; - using System.Threading.Tasks; - using TestStack.BDDfy; - using Xunit; - - public class AuthorisationMiddlewareTests - { - private readonly Mock _authService; - private readonly Mock _authScopesService; - private Mock _loggerFactory; - private Mock _logger; - private readonly AuthorisationMiddleware _middleware; - private readonly DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; - - public AuthorisationMiddlewareTests() - { - _authService = new Mock(); - _authScopesService = new Mock(); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - _loggerFactory = new Mock(); - _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = context => Task.CompletedTask; - _middleware = new AuthorisationMiddleware(_next, _authService.Object, _authScopesService.Object, _loggerFactory.Object); - } - - [Fact] - public void should_call_authorisation_service() - { + +namespace Ocelot.UnitTests.Authorization +{ + using Microsoft.AspNetCore.Http; + using Moq; + using Ocelot.Authorisation; + using Ocelot.Authorisation.Middleware; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Logging; + using Ocelot.Middleware; + using Ocelot.Responses; + using System.Collections.Generic; + using System.Security.Claims; + using System.Threading.Tasks; + using TestStack.BDDfy; + using Xunit; + + public class AuthorisationMiddlewareTests + { + private readonly Mock _authService; + private readonly Mock _authScopesService; + private Mock _loggerFactory; + private Mock _logger; + private readonly AuthorisationMiddleware _middleware; + private RequestDelegate _next; + private HttpContext _httpContext; + + public AuthorisationMiddlewareTests() + { + _httpContext = new DefaultHttpContext(); + _authService = new Mock(); + _authScopesService = new Mock(); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = context => Task.CompletedTask; + _middleware = new AuthorisationMiddleware(_next, _authService.Object, _authScopesService.Object, _loggerFactory.Object); + } + + [Fact] + public void should_call_authorisation_service() + { this.Given(x => x.GivenTheDownStreamRouteIs(new List(), - new DownstreamReRouteBuilder() - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().Build()) - .WithIsAuthorised(true) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build())) - .And(x => x.GivenTheAuthServiceReturns(new OkResponse(true))) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheAuthServiceIsCalledCorrectly()) - .BDDfy(); - } - - private void WhenICallTheMiddleware() - { - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - } - - private void GivenTheDownStreamRouteIs(List templatePlaceholderNameAndValues, DownstreamReRoute downstreamReRoute) - { - _downstreamContext.TemplatePlaceholderNameAndValues = templatePlaceholderNameAndValues; - _downstreamContext.DownstreamReRoute = downstreamReRoute; - } - - private void GivenTheAuthServiceReturns(Response expected) - { - _authService + new DownstreamReRouteBuilder() + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().Build()) + .WithIsAuthorised(true) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build())) + .And(x => x.GivenTheAuthServiceReturns(new OkResponse(true))) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheAuthServiceIsCalledCorrectly()) + .BDDfy(); + } + + private void WhenICallTheMiddleware() + { + _middleware.Invoke(_httpContext).GetAwaiter().GetResult(); + } + + private void GivenTheDownStreamRouteIs(List templatePlaceholderNameAndValues, DownstreamReRoute downstreamReRoute) + { + _httpContext.Items.UpsertTemplatePlaceholderNameAndValues(templatePlaceholderNameAndValues); + _httpContext.Items.UpsertDownstreamReRoute(downstreamReRoute); + } + + private void GivenTheAuthServiceReturns(Response expected) + { + _authService .Setup(x => x.Authorise( It.IsAny(), It.IsAny>(), - It.IsAny>())) - .Returns(expected); - } - - private void ThenTheAuthServiceIsCalledCorrectly() - { - _authService - .Verify(x => x.Authorise( - It.IsAny(), - It.IsAny>(), + It.IsAny>())) + .Returns(expected); + } + + private void ThenTheAuthServiceIsCalledCorrectly() + { + _authService + .Verify(x => x.Authorise( + It.IsAny(), + It.IsAny>(), It.IsAny>()) - , Times.Once); - } - } + , Times.Once); + } + } } diff --git a/test/Ocelot.UnitTests/Cache/CacheKeyGeneratorTests.cs b/test/Ocelot.UnitTests/Cache/CacheKeyGeneratorTests.cs index 6dc51319..d70481b9 100644 --- a/test/Ocelot.UnitTests/Cache/CacheKeyGeneratorTests.cs +++ b/test/Ocelot.UnitTests/Cache/CacheKeyGeneratorTests.cs @@ -1,38 +1,34 @@ -using Microsoft.AspNetCore.Http; -using Ocelot.Cache; -using Ocelot.Middleware; -using Shouldly; -using System.Net.Http; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Cache +namespace Ocelot.UnitTests.Cache { + using Ocelot.Cache; + using Ocelot.Request.Middleware; + using Shouldly; + using System.Net.Http; + using TestStack.BDDfy; + using Xunit; + public class CacheKeyGeneratorTests { private readonly ICacheKeyGenerator _cacheKeyGenerator; - private readonly DownstreamContext _downstreamContext; + private readonly DownstreamRequest _downstreamRequest; public CacheKeyGeneratorTests() { _cacheKeyGenerator = new CacheKeyGenerator(); _cacheKeyGenerator = new CacheKeyGenerator(); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()) - { - DownstreamRequest = new Ocelot.Request.Middleware.DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123")) - }; + _downstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123")); } [Fact] public void should_generate_cache_key_from_context() { - this.Given(x => x.GivenCacheKeyFromContext(_downstreamContext)) + this.Given(x => x.GivenCacheKeyFromContext(_downstreamRequest)) .BDDfy(); } - private void GivenCacheKeyFromContext(DownstreamContext context) + private void GivenCacheKeyFromContext(DownstreamRequest downstreamRequest) { - string generatedCacheKey = _cacheKeyGenerator.GenerateRequestCacheKey(context); + string generatedCacheKey = _cacheKeyGenerator.GenerateRequestCacheKey(downstreamRequest); string cachekey = MD5Helper.GenerateMd5("GET-https://some.url/blah?abcd=123"); generatedCacheKey.ShouldBe(cachekey); } diff --git a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs index bb936f50..abd45757 100644 --- a/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Cache/OutputCacheMiddlewareTests.cs @@ -1,140 +1,144 @@ -namespace Ocelot.UnitTests.Cache -{ - using Microsoft.AspNetCore.Http; - using Moq; - using Ocelot.Cache; - using Ocelot.Cache.Middleware; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using Ocelot.DownstreamRouteFinder; - using Ocelot.DownstreamRouteFinder.UrlMatcher; - using Ocelot.Logging; - using Ocelot.Middleware; - using System; - using System.Collections.Generic; - using System.Net; - using System.Net.Http; - using System.Threading.Tasks; - using TestStack.BDDfy; +namespace Ocelot.UnitTests.Cache +{ + using Microsoft.AspNetCore.Http; + using Moq; + using Ocelot.Cache; + using Ocelot.Cache.Middleware; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Logging; + using Ocelot.Middleware; + using System; + using System.Collections.Generic; + using System.Net; + using System.Net.Http; + using System.Threading.Tasks; + using Ocelot.Infrastructure.RequestData; + using TestStack.BDDfy; using Xunit; + using Ocelot.DownstreamRouteFinder.Middleware; - public class OutputCacheMiddlewareTests - { - private readonly Mock> _cache; - private readonly Mock _loggerFactory; - private Mock _logger; - private OutputCacheMiddleware _middleware; - private readonly DownstreamContext _downstreamContext; - private readonly OcelotRequestDelegate _next; - private readonly ICacheKeyGenerator _cacheKeyGenerator; - private CachedResponse _response; - - - public OutputCacheMiddlewareTests() - { - _cache = new Mock>(); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - _loggerFactory = new Mock(); - _logger = new Mock(); - _cacheKeyGenerator = new CacheKeyGenerator(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + public class OutputCacheMiddlewareTests + { + private readonly Mock> _cache; + private readonly Mock _loggerFactory; + private Mock _logger; + private OutputCacheMiddleware _middleware; + private readonly RequestDelegate _next; + private readonly ICacheKeyGenerator _cacheKeyGenerator; + private CachedResponse _response; + private HttpContext _httpContext; + private Mock _repo; + + public OutputCacheMiddlewareTests() + { + _repo = new Mock(); + _httpContext = new DefaultHttpContext(); + _cache = new Mock>(); + _loggerFactory = new Mock(); + _logger = new Mock(); + _cacheKeyGenerator = new CacheKeyGenerator(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _next = context => Task.CompletedTask; - _downstreamContext.DownstreamRequest = new Ocelot.Request.Middleware.DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123")); - } - - [Fact] - public void should_returned_cached_item_when_it_is_in_cache() + _httpContext.Items.UpsertDownstreamRequest(new Ocelot.Request.Middleware.DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "https://some.url/blah?abcd=123"))); + } + + [Fact] + public void should_returned_cached_item_when_it_is_in_cache() + { + var headers = new Dictionary> + { + { "test", new List { "test" } } + }; + + var contentHeaders = new Dictionary> + { + { "content-type", new List { "application/json" } } + }; + + var cachedResponse = new CachedResponse(HttpStatusCode.OK, headers, "", contentHeaders, "some reason"); + this.Given(x => x.GivenThereIsACachedResponse(cachedResponse)) + .And(x => x.GivenTheDownstreamRouteIs()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheCacheGetIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_returned_cached_item_when_it_is_in_cache_expires_header() + { + var contentHeaders = new Dictionary> + { + { "Expires", new List { "-1" } } + }; + + var cachedResponse = new CachedResponse(HttpStatusCode.OK, new Dictionary>(), "", contentHeaders, "some reason"); + this.Given(x => x.GivenThereIsACachedResponse(cachedResponse)) + .And(x => x.GivenTheDownstreamRouteIs()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheCacheGetIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_continue_with_pipeline_and_cache_response() + { + this.Given(x => x.GivenResponseIsNotCached(new HttpResponseMessage())) + .And(x => x.GivenTheDownstreamRouteIs()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheCacheAddIsCalledCorrectly()) + .BDDfy(); + } + + private void WhenICallTheMiddleware() + { + _middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cache.Object, _cacheKeyGenerator); + _middleware.Invoke(_httpContext).GetAwaiter().GetResult(); + } + + private void GivenThereIsACachedResponse(CachedResponse response) + { + _response = response; + _cache + .Setup(x => x.Get(It.IsAny(), It.IsAny())) + .Returns(_response); + } + + private void GivenResponseIsNotCached(HttpResponseMessage responseMessage) { - var headers = new Dictionary> - { - { "test", new List { "test" } } - }; - - var contentHeaders = new Dictionary> - { - { "content-type", new List { "application/json" } } - }; - - var cachedResponse = new CachedResponse(HttpStatusCode.OK, headers, "", contentHeaders, "some reason"); - this.Given(x => x.GivenThereIsACachedResponse(cachedResponse)) - .And(x => x.GivenTheDownstreamRouteIs()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheCacheGetIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_returned_cached_item_when_it_is_in_cache_expires_header() - { - var contentHeaders = new Dictionary> - { - { "Expires", new List { "-1" } } - }; - - var cachedResponse = new CachedResponse(HttpStatusCode.OK, new Dictionary>(), "", contentHeaders, "some reason"); - this.Given(x => x.GivenThereIsACachedResponse(cachedResponse)) - .And(x => x.GivenTheDownstreamRouteIs()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheCacheGetIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_continue_with_pipeline_and_cache_response() - { - this.Given(x => x.GivenResponseIsNotCached(new HttpResponseMessage())) - .And(x => x.GivenTheDownstreamRouteIs()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheCacheAddIsCalledCorrectly()) - .BDDfy(); - } - - private void WhenICallTheMiddleware() - { - _middleware = new OutputCacheMiddleware(_next, _loggerFactory.Object, _cache.Object, _cacheKeyGenerator); - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - } - - private void GivenThereIsACachedResponse(CachedResponse response) - { - _response = response; - _cache - .Setup(x => x.Get(It.IsAny(), It.IsAny())) - .Returns(_response); - } - - private void GivenResponseIsNotCached(HttpResponseMessage responseMessage) - { - _downstreamContext.DownstreamResponse = new DownstreamResponse(responseMessage); - } - - private void GivenTheDownstreamRouteIs() - { - var reRoute = new ReRouteBuilder() - .WithDownstreamReRoute(new DownstreamReRouteBuilder() - .WithIsCached(true) - .WithCacheOptions(new CacheOptions(100, "kanken")) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build(); - + _httpContext.Items.UpsertDownstreamResponse(new DownstreamResponse(responseMessage)); + } + + private void GivenTheDownstreamRouteIs() + { + var reRoute = new ReRouteBuilder() + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithIsCached(true) + .WithCacheOptions(new CacheOptions(100, "kanken")) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build(); + var downstreamRoute = new DownstreamRoute(new List(), reRoute); - _downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues; - _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; - } + _httpContext.Items.UpsertTemplatePlaceholderNameAndValues(downstreamRoute.TemplatePlaceholderNameAndValues); - private void ThenTheCacheGetIsCalledCorrectly() - { - _cache - .Verify(x => x.Get(It.IsAny(), It.IsAny()), Times.Once); - } - - private void ThenTheCacheAddIsCalledCorrectly() - { - _cache - .Verify(x => x.Add(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); - } - } -} + _httpContext.Items.UpsertDownstreamReRoute(downstreamRoute.ReRoute.DownstreamReRoute[0]); + } + + private void ThenTheCacheGetIsCalledCorrectly() + { + _cache + .Verify(x => x.Get(It.IsAny(), It.IsAny()), Times.Once); + } + + private void ThenTheCacheAddIsCalledCorrectly() + { + _cache + .Verify(x => x.Add(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); + } + } +} diff --git a/test/Ocelot.UnitTests/CacheManager/OutputCacheMiddlewareRealCacheTests.cs b/test/Ocelot.UnitTests/CacheManager/OutputCacheMiddlewareRealCacheTests.cs index 0a8c23b6..8d0c5b31 100644 --- a/test/Ocelot.UnitTests/CacheManager/OutputCacheMiddlewareRealCacheTests.cs +++ b/test/Ocelot.UnitTests/CacheManager/OutputCacheMiddlewareRealCacheTests.cs @@ -1,96 +1,97 @@ -namespace Ocelot.UnitTests.CacheManager -{ - using global::CacheManager.Core; - using Microsoft.AspNetCore.Http; - using Moq; - using Ocelot.Cache; - using Ocelot.Cache.CacheManager; - using Ocelot.Cache.Middleware; - using Ocelot.Configuration; +namespace Ocelot.UnitTests.CacheManager +{ + using global::CacheManager.Core; + using Microsoft.AspNetCore.Http; + using Moq; + using Ocelot.Cache; + using Ocelot.Cache.CacheManager; + using Ocelot.Cache.Middleware; + using Ocelot.Configuration; using Ocelot.Configuration.Builder; - using Ocelot.Logging; - using Ocelot.Middleware; - using Shouldly; - using System.Collections.Generic; - using System.Linq; - using System.Net; - using System.Net.Http; - using System.Net.Http.Headers; - using System.Threading.Tasks; - using TestStack.BDDfy; - using Xunit; - - public class OutputCacheMiddlewareRealCacheTests - { - private readonly IOcelotCache _cacheManager; - private readonly ICacheKeyGenerator _cacheKeyGenerator; - private readonly OutputCacheMiddleware _middleware; - private readonly DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; - private Mock _loggerFactory; - private Mock _logger; - - public OutputCacheMiddlewareRealCacheTests() - { - _loggerFactory = new Mock(); - _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", x => - { - x.WithDictionaryHandle(); - }); - _cacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache); + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.Logging; + using Ocelot.Middleware; + using Shouldly; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Net.Http.Headers; + using System.Threading.Tasks; + using TestStack.BDDfy; + using Xunit; + + public class OutputCacheMiddlewareRealCacheTests + { + private readonly IOcelotCache _cacheManager; + private readonly ICacheKeyGenerator _cacheKeyGenerator; + private readonly OutputCacheMiddleware _middleware; + private RequestDelegate _next; + private Mock _loggerFactory; + private Mock _logger; + private HttpContext _httpContext; + + public OutputCacheMiddlewareRealCacheTests() + { + _httpContext = new DefaultHttpContext(); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + var cacheManagerOutputCache = CacheFactory.Build("OcelotOutputCache", x => + { + x.WithDictionaryHandle(); + }); + _cacheManager = new OcelotCacheManagerCache(cacheManagerOutputCache); _cacheKeyGenerator = new CacheKeyGenerator(); - _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, _cacheKeyGenerator); - } - - [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>>(), "fooreason"); - - 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() - { - string cacheKey = MD5Helper.GenerateMd5("GET-https://some.url/blah?abcd=123"); - var result = _cacheManager.Get(cacheKey, "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" }) + _httpContext.Items.UpsertDownstreamRequest(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, _cacheKeyGenerator); + } + + [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>>(), "fooreason"); + + this.Given(x => x.GivenResponseIsNotCached(response)) + .And(x => x.GivenTheDownstreamRouteIs()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheContentTypeHeaderIsCached()) + .BDDfy(); + } + + private void WhenICallTheMiddleware() + { + _middleware.Invoke(_httpContext).GetAwaiter().GetResult(); + } + + private void ThenTheContentTypeHeaderIsCached() + { + string cacheKey = MD5Helper.GenerateMd5("GET-https://some.url/blah?abcd=123"); + var result = _cacheManager.Get(cacheKey, "kanken"); + var header = result.ContentHeaders["Content-Type"]; + header.First().ShouldBe("application/json"); + } + + private void GivenResponseIsNotCached(DownstreamResponse response) + { + _httpContext.Items.UpsertDownstreamResponse(response); + } + + private void GivenTheDownstreamRouteIs() + { + var reRoute = new DownstreamReRouteBuilder() + .WithIsCached(true) + .WithCacheOptions(new CacheOptions(100, "kanken")) + .WithUpstreamHttpMethod(new List { "Get" }) .Build(); - _downstreamContext.DownstreamReRoute = reRoute; - } - } -} + _httpContext.Items.UpsertDownstreamReRoute(reRoute); + } + } +} diff --git a/test/Ocelot.UnitTests/Claims/AddClaimsToRequestTests.cs b/test/Ocelot.UnitTests/Claims/AddClaimsToRequestTests.cs index cc6428f4..c0b9a55b 100644 --- a/test/Ocelot.UnitTests/Claims/AddClaimsToRequestTests.cs +++ b/test/Ocelot.UnitTests/Claims/AddClaimsToRequestTests.cs @@ -1,142 +1,142 @@ -using Microsoft.AspNetCore.Http; -using Moq; -using Ocelot.Claims; -using Ocelot.Configuration; -using Ocelot.Errors; -using Ocelot.Infrastructure.Claims.Parser; -using Ocelot.Responses; -using Shouldly; -using System.Collections.Generic; -using System.Security.Claims; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Claims -{ - public class AddClaimsToRequestTests - { - private readonly AddClaimsToRequest _addClaimsToRequest; - private readonly Mock _parser; - private List _claimsToThings; - private HttpContext _context; - private Response _result; - private Response _claimValue; - - public AddClaimsToRequestTests() - { - _parser = new Mock(); - _addClaimsToRequest = new AddClaimsToRequest(_parser.Object); - } - - [Fact] - public void should_add_claims_to_context() - { - var context = new DefaultHttpContext - { - User = new ClaimsPrincipal(new ClaimsIdentity(new List - { - new Claim("test", "data") - })) - }; - - this.Given( - x => x.GivenClaimsToThings(new List - { - new ClaimToThing("claim-key", "", "", 0) - })) - .Given(x => x.GivenHttpContext(context)) - .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) - .When(x => x.WhenIAddClaimsToTheRequest()) - .Then(x => x.ThenTheResultIsSuccess()) - .BDDfy(); - } - - [Fact] - public void if_claims_exists_should_replace_it() - { - var context = new DefaultHttpContext - { - User = new ClaimsPrincipal(new ClaimsIdentity(new List - { - new Claim("existing-key", "data"), - new Claim("new-key", "data") - })), - }; - - this.Given( - x => x.GivenClaimsToThings(new List - { - new ClaimToThing("existing-key", "new-key", "", 0) - })) - .Given(x => x.GivenHttpContext(context)) - .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) - .When(x => x.WhenIAddClaimsToTheRequest()) - .Then(x => x.ThenTheResultIsSuccess()) - .BDDfy(); - } - - [Fact] - public void should_return_error() - { - this.Given( - x => x.GivenClaimsToThings(new List - { - new ClaimToThing("", "", "", 0) - })) - .Given(x => x.GivenHttpContext(new DefaultHttpContext())) - .And(x => x.GivenTheClaimParserReturns(new ErrorResponse(new List - { - new AnyError() - }))) - .When(x => x.WhenIAddClaimsToTheRequest()) - .Then(x => x.ThenTheResultIsError()) - .BDDfy(); - } - - private void GivenClaimsToThings(List configuration) - { - _claimsToThings = configuration; - } - - private void GivenHttpContext(HttpContext context) - { - _context = context; - } - - private void GivenTheClaimParserReturns(Response claimValue) - { - _claimValue = claimValue; - _parser - .Setup( - x => - x.GetValue(It.IsAny>(), - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns(_claimValue); - } - - private void WhenIAddClaimsToTheRequest() - { - _result = _addClaimsToRequest.SetClaimsOnContext(_claimsToThings, _context); - } - - private void ThenTheResultIsSuccess() - { - _result.IsError.ShouldBe(false); - } - - private void ThenTheResultIsError() - { - _result.IsError.ShouldBe(true); - } - - private class AnyError : Error - { - public AnyError() - : base("blahh", OcelotErrorCode.UnknownError) - { - } - } - } +using Microsoft.AspNetCore.Http; +using Moq; +using Ocelot.Claims; +using Ocelot.Configuration; +using Ocelot.Errors; +using Ocelot.Infrastructure.Claims.Parser; +using Ocelot.Responses; +using Shouldly; +using System.Collections.Generic; +using System.Security.Claims; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Claims +{ + public class AddClaimsToRequestTests + { + private readonly AddClaimsToRequest _addClaimsToRequest; + private readonly Mock _parser; + private List _claimsToThings; + private HttpContext _context; + private Response _result; + private Response _claimValue; + + public AddClaimsToRequestTests() + { + _parser = new Mock(); + _addClaimsToRequest = new AddClaimsToRequest(_parser.Object); + } + + [Fact] + public void should_add_claims_to_context() + { + var context = new DefaultHttpContext + { + User = new ClaimsPrincipal(new ClaimsIdentity(new List + { + new Claim("test", "data") + })) + }; + + this.Given( + x => x.GivenClaimsToThings(new List + { + new ClaimToThing("claim-key", "", "", 0) + })) + .Given(x => x.GivenHttpContext(context)) + .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) + .When(x => x.WhenIAddClaimsToTheRequest()) + .Then(x => x.ThenTheResultIsSuccess()) + .BDDfy(); + } + + [Fact] + public void if_claims_exists_should_replace_it() + { + var context = new DefaultHttpContext + { + User = new ClaimsPrincipal(new ClaimsIdentity(new List + { + new Claim("existing-key", "data"), + new Claim("new-key", "data") + })), + }; + + this.Given( + x => x.GivenClaimsToThings(new List + { + new ClaimToThing("existing-key", "new-key", "", 0) + })) + .Given(x => x.GivenHttpContext(context)) + .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) + .When(x => x.WhenIAddClaimsToTheRequest()) + .Then(x => x.ThenTheResultIsSuccess()) + .BDDfy(); + } + + [Fact] + public void should_return_error() + { + this.Given( + x => x.GivenClaimsToThings(new List + { + new ClaimToThing("", "", "", 0) + })) + .Given(x => x.GivenHttpContext(new DefaultHttpContext())) + .And(x => x.GivenTheClaimParserReturns(new ErrorResponse(new List + { + new AnyError() + }))) + .When(x => x.WhenIAddClaimsToTheRequest()) + .Then(x => x.ThenTheResultIsError()) + .BDDfy(); + } + + private void GivenClaimsToThings(List configuration) + { + _claimsToThings = configuration; + } + + private void GivenHttpContext(HttpContext context) + { + _context = context; + } + + private void GivenTheClaimParserReturns(Response claimValue) + { + _claimValue = claimValue; + _parser + .Setup( + x => + x.GetValue(It.IsAny>(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(_claimValue); + } + + private void WhenIAddClaimsToTheRequest() + { + _result = _addClaimsToRequest.SetClaimsOnContext(_claimsToThings, _context); + } + + private void ThenTheResultIsSuccess() + { + _result.IsError.ShouldBe(false); + } + + private void ThenTheResultIsError() + { + _result.IsError.ShouldBe(true); + } + + private class AnyError : Error + { + public AnyError() + : base("blahh", OcelotErrorCode.UnknownError, 404) + { + } + } + } } diff --git a/test/Ocelot.UnitTests/Claims/ClaimsToClaimsMiddlewareTests.cs b/test/Ocelot.UnitTests/Claims/ClaimsToClaimsMiddlewareTests.cs index 7652aeee..29ee3f4e 100644 --- a/test/Ocelot.UnitTests/Claims/ClaimsToClaimsMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Claims/ClaimsToClaimsMiddlewareTests.cs @@ -1,89 +1,91 @@ -using Ocelot.Middleware; - -namespace Ocelot.UnitTests.Claims -{ - using Microsoft.AspNetCore.Http; - using Moq; - using Ocelot.Claims; - using Ocelot.Claims.Middleware; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; +using Ocelot.Middleware; + +namespace Ocelot.UnitTests.Claims +{ + using Microsoft.AspNetCore.Http; + using Moq; + using Ocelot.Claims; + using Ocelot.Claims.Middleware; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; using Ocelot.DownstreamRouteFinder; - using Ocelot.DownstreamRouteFinder.UrlMatcher; - using Ocelot.Logging; - using Ocelot.Responses; - using System.Collections.Generic; - using System.Threading.Tasks; - using TestStack.BDDfy; - using Xunit; - - public class ClaimsToClaimsMiddlewareTests - { - private readonly Mock _addHeaders; - private Mock _loggerFactory; - private Mock _logger; - private readonly ClaimsToClaimsMiddleware _middleware; - private readonly DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; - - public ClaimsToClaimsMiddlewareTests() + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Logging; + using Ocelot.Responses; + using System.Collections.Generic; + using System.Threading.Tasks; + using TestStack.BDDfy; + using Xunit; + + public class ClaimsToClaimsMiddlewareTests + { + private readonly Mock _addHeaders; + private Mock _loggerFactory; + private Mock _logger; + private readonly ClaimsToClaimsMiddleware _middleware; + private RequestDelegate _next; + private HttpContext _httpContext; + + public ClaimsToClaimsMiddlewareTests() + { + _httpContext = new DefaultHttpContext(); + _addHeaders = new Mock(); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = context => Task.CompletedTask; + _middleware = new ClaimsToClaimsMiddleware(_next, _loggerFactory.Object, _addHeaders.Object); + } + + [Fact] + public void should_call_claims_to_request_correctly() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithClaimsToClaims(new List + { + new ClaimToThing("sub", "UserType", "|", 0) + }) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => x.GivenTheAddClaimsToRequestReturns()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheClaimsToRequestIsCalledCorrectly()) + .BDDfy(); + } + + private void WhenICallTheMiddleware() + { + _middleware.Invoke(_httpContext).GetAwaiter().GetResult(); + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) { - _addHeaders = new Mock(); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - _loggerFactory = new Mock(); - _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = context => Task.CompletedTask; - _middleware = new ClaimsToClaimsMiddleware(_next, _loggerFactory.Object, _addHeaders.Object); - } + _httpContext.Items.UpsertTemplatePlaceholderNameAndValues(downstreamRoute.TemplatePlaceholderNameAndValues); - [Fact] - public void should_call_claims_to_request_correctly() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithClaimsToClaims(new List - { - new ClaimToThing("sub", "UserType", "|", 0) - }) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => x.GivenTheAddClaimsToRequestReturns()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheClaimsToRequestIsCalledCorrectly()) - .BDDfy(); - } - - private void WhenICallTheMiddleware() - { - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - } - - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues; - _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; - } - - private void GivenTheAddClaimsToRequestReturns() - { - _addHeaders - .Setup(x => x.SetClaimsOnContext(It.IsAny>(), - It.IsAny())) - .Returns(new OkResponse()); - } - - private void ThenTheClaimsToRequestIsCalledCorrectly() - { - _addHeaders - .Verify(x => x.SetClaimsOnContext(It.IsAny>(), - It.IsAny()), Times.Once); - } - } -} + _httpContext.Items.UpsertDownstreamReRoute(downstreamRoute.ReRoute.DownstreamReRoute[0]); + } + + private void GivenTheAddClaimsToRequestReturns() + { + _addHeaders + .Setup(x => x.SetClaimsOnContext(It.IsAny>(), + It.IsAny())) + .Returns(new OkResponse()); + } + + private void ThenTheClaimsToRequestIsCalledCorrectly() + { + _addHeaders + .Verify(x => x.SetClaimsOnContext(It.IsAny>(), + It.IsAny()), Times.Once); + } + } +} diff --git a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs index fbf1cae2..d7637915 100644 --- a/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs +++ b/test/Ocelot.UnitTests/Controllers/FileConfigurationControllerTests.cs @@ -1,135 +1,135 @@ -using Microsoft.AspNetCore.Mvc; -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.File; -using Ocelot.Configuration.Setter; -using Ocelot.Errors; -using Ocelot.Responses; -using Shouldly; -using System; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Controllers -{ - using Ocelot.Configuration.Repository; - - public class FileConfigurationControllerTests - { - private readonly FileConfigurationController _controller; - private readonly Mock _repo; - private readonly Mock _setter; - private IActionResult _result; - private FileConfiguration _fileConfiguration; - private readonly Mock _provider; - - public FileConfigurationControllerTests() - { - _provider = new Mock(); - _repo = new Mock(); - _setter = new Mock(); - _controller = new FileConfigurationController(_repo.Object, _setter.Object, _provider.Object); +using Microsoft.AspNetCore.Mvc; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.File; +using Ocelot.Configuration.Setter; +using Ocelot.Errors; +using Ocelot.Responses; +using Shouldly; +using System; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Controllers +{ + using Ocelot.Configuration.Repository; + + public class FileConfigurationControllerTests + { + private readonly FileConfigurationController _controller; + private readonly Mock _repo; + private readonly Mock _setter; + private IActionResult _result; + private FileConfiguration _fileConfiguration; + private readonly Mock _provider; + + public FileConfigurationControllerTests() + { + _provider = new Mock(); + _repo = new Mock(); + _setter = new Mock(); + _controller = new FileConfigurationController(_repo.Object, _setter.Object, _provider.Object); } - [Fact] - public void should_get_file_configuration() - { - var expected = new Responses.OkResponse(new FileConfiguration()); - - this.Given(x => x.GivenTheGetConfigurationReturns(expected)) - .When(x => x.WhenIGetTheFileConfiguration()) - .Then(x => x.TheTheGetFileConfigurationIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_return_error_when_cannot_get_config() - { + [Fact] + public void should_get_file_configuration() + { + var expected = new Responses.OkResponse(new FileConfiguration()); + + this.Given(x => x.GivenTheGetConfigurationReturns(expected)) + .When(x => x.WhenIGetTheFileConfiguration()) + .Then(x => x.TheTheGetFileConfigurationIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_return_error_when_cannot_get_config() + { var expected = new Responses.ErrorResponse(It.IsAny()); this.Given(x => x.GivenTheGetConfigurationReturns(expected)) .When(x => x.WhenIGetTheFileConfiguration()) .Then(x => x.TheTheGetFileConfigurationIsCalledCorrectly()) .And(x => x.ThenTheResponseIs()) - .BDDfy(); + .BDDfy(); } - [Fact] - public void should_post_file_configuration() - { - var expected = new FileConfiguration(); - - this.Given(x => GivenTheFileConfiguration(expected)) - .And(x => GivenTheConfigSetterReturns(new OkResponse())) - .When(x => WhenIPostTheFileConfiguration()) - .Then(x => x.ThenTheConfigrationSetterIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_return_error_when_cannot_set_config() - { - var expected = new FileConfiguration(); - - this.Given(x => GivenTheFileConfiguration(expected)) - .And(x => GivenTheConfigSetterReturns(new ErrorResponse(new FakeError()))) - .When(x => WhenIPostTheFileConfiguration()) - .Then(x => x.ThenTheConfigrationSetterIsCalledCorrectly()) - .And(x => ThenTheResponseIs()) - .BDDfy(); - } - - private void GivenTheConfigSetterReturns(Response response) - { - _setter - .Setup(x => x.Set(It.IsAny())) - .ReturnsAsync(response); - } - - private void ThenTheConfigrationSetterIsCalledCorrectly() - { - _setter - .Verify(x => x.Set(_fileConfiguration), Times.Once); - } - - private void WhenIPostTheFileConfiguration() - { - _result = _controller.Post(_fileConfiguration).Result; - } - - private void GivenTheFileConfiguration(FileConfiguration fileConfiguration) - { - _fileConfiguration = fileConfiguration; - } - - private void ThenTheResponseIs() + [Fact] + public void should_post_file_configuration() { - _result.ShouldBeOfType(); - } - - private void GivenTheGetConfigurationReturns(Ocelot.Responses.Response fileConfiguration) - { - _repo - .Setup(x => x.Get()) - .ReturnsAsync(fileConfiguration); - } - - private void WhenIGetTheFileConfiguration() - { - _result = _controller.Get().Result; - } - - private void TheTheGetFileConfigurationIsCalledCorrectly() + var expected = new FileConfiguration(); + + this.Given(x => GivenTheFileConfiguration(expected)) + .And(x => GivenTheConfigSetterReturns(new OkResponse())) + .When(x => WhenIPostTheFileConfiguration()) + .Then(x => x.ThenTheConfigrationSetterIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_return_error_when_cannot_set_config() + { + var expected = new FileConfiguration(); + + this.Given(x => GivenTheFileConfiguration(expected)) + .And(x => GivenTheConfigSetterReturns(new ErrorResponse(new FakeError()))) + .When(x => WhenIPostTheFileConfiguration()) + .Then(x => x.ThenTheConfigrationSetterIsCalledCorrectly()) + .And(x => ThenTheResponseIs()) + .BDDfy(); + } + + private void GivenTheConfigSetterReturns(Response response) + { + _setter + .Setup(x => x.Set(It.IsAny())) + .ReturnsAsync(response); + } + + private void ThenTheConfigrationSetterIsCalledCorrectly() + { + _setter + .Verify(x => x.Set(_fileConfiguration), Times.Once); + } + + private void WhenIPostTheFileConfiguration() + { + _result = _controller.Post(_fileConfiguration).Result; + } + + private void GivenTheFileConfiguration(FileConfiguration fileConfiguration) + { + _fileConfiguration = fileConfiguration; + } + + private void ThenTheResponseIs() + { + _result.ShouldBeOfType(); + } + + private void GivenTheGetConfigurationReturns(Ocelot.Responses.Response fileConfiguration) { _repo - .Verify(x => x.Get(), Times.Once); - } - - private class FakeError : Error - { - public FakeError() : base(string.Empty, OcelotErrorCode.CannotAddDataError) - { - } - } - } + .Setup(x => x.Get()) + .ReturnsAsync(fileConfiguration); + } + + private void WhenIGetTheFileConfiguration() + { + _result = _controller.Get().Result; + } + + private void TheTheGetFileConfigurationIsCalledCorrectly() + { + _repo + .Verify(x => x.Get(), Times.Once); + } + + private class FakeError : Error + { + public FakeError() : base(string.Empty, OcelotErrorCode.CannotAddDataError, 404) + { + } + } + } } diff --git a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs index 1a3ac2cb..121634e5 100644 --- a/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs +++ b/test/Ocelot.UnitTests/DependencyInjection/OcelotBuilderTests.cs @@ -1,404 +1,403 @@ -using System.Threading.Tasks; -using Ocelot.LoadBalancer.LoadBalancers; -using Ocelot.Middleware; -using Ocelot.Responses; -using Ocelot.Values; +namespace Ocelot.UnitTests.DependencyInjection +{ + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Moq; + using Ocelot.Configuration.Setter; + using Ocelot.DependencyInjection; + using Ocelot.Infrastructure; + using Ocelot.Multiplexer; + using Ocelot.Requester; + using Ocelot.UnitTests.Requester; + using Shouldly; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Reflection; + using Microsoft.AspNetCore.Http; + using TestStack.BDDfy; + using Xunit; + using System.Threading.Tasks; + using Ocelot.LoadBalancer.LoadBalancers; + using Ocelot.Responses; + using Ocelot.Values; + using static Ocelot.UnitTests.Multiplexing.UserDefinedResponseAggregatorTests; -namespace Ocelot.UnitTests.DependencyInjection -{ - using Microsoft.AspNetCore.Hosting; - using Microsoft.Extensions.Configuration; - using Microsoft.Extensions.DependencyInjection; - using Moq; - using Ocelot.Configuration.Setter; - using Ocelot.DependencyInjection; - using Ocelot.Infrastructure; - using Ocelot.Middleware.Multiplexer; - using Ocelot.Requester; - using Ocelot.UnitTests.Requester; - using Shouldly; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Net.Http; - using System.Reflection; - using TestStack.BDDfy; - using Xunit; - using static Ocelot.UnitTests.Middleware.UserDefinedResponseAggregatorTests; - - public class OcelotBuilderTests - { - private readonly IServiceCollection _services; - private IServiceProvider _serviceProvider; - private readonly IConfiguration _configRoot; - private IOcelotBuilder _ocelotBuilder; - private readonly int _maxRetries; - private Exception _ex; - - public OcelotBuilderTests() - { - _configRoot = new ConfigurationRoot(new List()); - _services = new ServiceCollection(); - _services.AddSingleton(GetHostingEnvironment()); - _services.AddSingleton(_configRoot); - _maxRetries = 100; - } - - private IWebHostEnvironment GetHostingEnvironment() - { - var environment = new Mock(); - environment - .Setup(e => e.ApplicationName) - .Returns(typeof(OcelotBuilderTests).GetTypeInfo().Assembly.GetName().Name); - - return environment.Object; - } - - [Fact] - public void should_add_specific_delegating_handlers_transient() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => AddSpecificTransientDelegatingHandler()) - .And(x => AddSpecificTransientDelegatingHandler()) - .Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificHandlers()) - .And(x => ThenTheSpecificHandlersAreTransient()) - .BDDfy(); - } - - [Fact] - public void should_add_type_specific_delegating_handlers_transient() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => AddTypeSpecificTransientDelegatingHandler(typeof(FakeDelegatingHandler))) - .And(x => AddTypeSpecificTransientDelegatingHandler(typeof(FakeDelegatingHandlerTwo))) - .Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificHandlers()) - .And(x => ThenTheSpecificHandlersAreTransient()) - .BDDfy(); - } - - [Fact] - public void should_add_global_delegating_handlers_transient() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => AddTransientGlobalDelegatingHandler()) - .And(x => AddTransientGlobalDelegatingHandler()) - .Then(x => ThenTheProviderIsRegisteredAndReturnsHandlers()) - .And(x => ThenTheGlobalHandlersAreTransient()) - .BDDfy(); - } - - [Fact] - public void should_add_global_type_delegating_handlers_transient() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => AddTransientGlobalDelegatingHandler()) - .And(x => AddTransientGlobalDelegatingHandler()) - .Then(x => ThenTheProviderIsRegisteredAndReturnsHandlers()) - .And(x => ThenTheGlobalHandlersAreTransient()) - .BDDfy(); - } - - [Fact] - public void should_set_up_services() - { - this.When(x => WhenISetUpOcelotServices()) - .Then(x => ThenAnExceptionIsntThrown()) - .BDDfy(); - } - - [Fact] - public void should_return_ocelot_builder() - { - this.When(x => WhenISetUpOcelotServices()) - .Then(x => ThenAnOcelotBuilderIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_use_logger_factory() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => WhenIValidateScopes()) - .When(x => WhenIAccessLoggerFactory()) - .Then(x => ThenAnExceptionIsntThrown()) - .BDDfy(); - } - - [Fact] - public void should_set_up_without_passing_in_config() - { - this.When(x => WhenISetUpOcelotServicesWithoutConfig()) - .Then(x => ThenAnExceptionIsntThrown()) - .BDDfy(); - } - - [Fact] - public void should_add_singleton_defined_aggregators() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => AddSingletonDefinedAggregator()) - .When(x => AddSingletonDefinedAggregator()) - .Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificAggregators()) - .And(x => ThenTheAggregatorsAreSingleton()) - .BDDfy(); - } - - [Fact] - public void should_add_transient_defined_aggregators() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => AddTransientDefinedAggregator()) - .When(x => AddTransientDefinedAggregator()) - .Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificAggregators()) - .And(x => ThenTheAggregatorsAreTransient()) - .BDDfy(); - } - - [Fact] - public void should_add_custom_load_balancer_creators_by_default_ctor() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => _ocelotBuilder.AddCustomLoadBalancer()) - .Then(x => ThenTheProviderIsRegisteredAndReturnsBothBuiltInAndCustomLoadBalancerCreators()) - .BDDfy(); - } - - [Fact] - public void should_add_custom_load_balancer_creators_by_factory_method() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => _ocelotBuilder.AddCustomLoadBalancer(() => new FakeCustomLoadBalancer())) - .Then(x => ThenTheProviderIsRegisteredAndReturnsBothBuiltInAndCustomLoadBalancerCreators()) - .BDDfy(); - } - - [Fact] - public void should_add_custom_load_balancer_creators_by_di_factory_method() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => _ocelotBuilder.AddCustomLoadBalancer(provider => new FakeCustomLoadBalancer())) - .Then(x => ThenTheProviderIsRegisteredAndReturnsBothBuiltInAndCustomLoadBalancerCreators()) - .BDDfy(); - } - - [Fact] - public void should_add_custom_load_balancer_creators_by_factory_method_with_arguments() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => _ocelotBuilder.AddCustomLoadBalancer((reroute, discoveryProvider) => new FakeCustomLoadBalancer())) - .Then(x => ThenTheProviderIsRegisteredAndReturnsBothBuiltInAndCustomLoadBalancerCreators()) - .BDDfy(); - } - - [Fact] - public void should_replace_iplaceholder() - { - this.Given(x => x.WhenISetUpOcelotServices()) - .When(x => AddConfigPlaceholders()) - .Then(x => ThenAnExceptionIsntThrown()) - .And(x => ThenTheIPlaceholderInstanceIsReplaced()) - .BDDfy(); - } - - [Fact] - public void should_add_custom_load_balancer_creators() - { - this.Given(x => WhenISetUpOcelotServices()) - .When(x => _ocelotBuilder.AddCustomLoadBalancer((provider, reroute, discoveryProvider) => new FakeCustomLoadBalancer())) - .Then(x => ThenTheProviderIsRegisteredAndReturnsBothBuiltInAndCustomLoadBalancerCreators()) - .BDDfy(); - } - - private void AddSingletonDefinedAggregator() - where T : class, IDefinedAggregator - { - _ocelotBuilder.AddSingletonDefinedAggregator(); - } - - private void AddTransientDefinedAggregator() - where T : class, IDefinedAggregator - { - _ocelotBuilder.AddTransientDefinedAggregator(); - } - - private void AddConfigPlaceholders() - { - _ocelotBuilder.AddConfigPlaceholders(); - } - - private void ThenTheSpecificHandlersAreTransient() - { - var handlers = _serviceProvider.GetServices().ToList(); - var first = handlers[0]; - handlers = _serviceProvider.GetServices().ToList(); - var second = handlers[0]; - first.ShouldNotBe(second); - } - - private void ThenTheGlobalHandlersAreTransient() - { - var handlers = _serviceProvider.GetServices().ToList(); - var first = handlers[0].DelegatingHandler; - handlers = _serviceProvider.GetServices().ToList(); - var second = handlers[0].DelegatingHandler; - first.ShouldNotBe(second); - } - - private void AddTransientGlobalDelegatingHandler() - where T : DelegatingHandler - { - _ocelotBuilder.AddDelegatingHandler(true); - } - - private void AddSpecificTransientDelegatingHandler() - where T : DelegatingHandler - { - _ocelotBuilder.AddDelegatingHandler(); - } - - private void AddTypeTransientGlobalDelegatingHandler(Type type) - { - _ocelotBuilder.AddDelegatingHandler(type, true); - } - - private void AddTypeSpecificTransientDelegatingHandler(Type type) - { - _ocelotBuilder.AddDelegatingHandler(type); - } - - private void ThenTheProviderIsRegisteredAndReturnsHandlers() - { - _serviceProvider = _services.BuildServiceProvider(); - var handlers = _serviceProvider.GetServices().ToList(); - handlers[0].DelegatingHandler.ShouldBeOfType(); - handlers[1].DelegatingHandler.ShouldBeOfType(); - } - - private void ThenTheProviderIsRegisteredAndReturnsSpecificHandlers() - { - _serviceProvider = _services.BuildServiceProvider(); - var handlers = _serviceProvider.GetServices().ToList(); - handlers[0].ShouldBeOfType(); - handlers[1].ShouldBeOfType(); - } - - private void ThenTheProviderIsRegisteredAndReturnsSpecificAggregators() - { - _serviceProvider = _services.BuildServiceProvider(); - var handlers = _serviceProvider.GetServices().ToList(); - handlers[0].ShouldBeOfType(); - handlers[1].ShouldBeOfType(); - } - - private void ThenTheProviderIsRegisteredAndReturnsBothBuiltInAndCustomLoadBalancerCreators() - { - _serviceProvider = _services.BuildServiceProvider(); - var creators = _serviceProvider.GetServices().ToList(); - creators.Count(c => c.GetType() == typeof(NoLoadBalancerCreator)).ShouldBe(1); - creators.Count(c => c.GetType() == typeof(RoundRobinCreator)).ShouldBe(1); - creators.Count(c => c.GetType() == typeof(CookieStickySessionsCreator)).ShouldBe(1); - creators.Count(c => c.GetType() == typeof(LeastConnectionCreator)).ShouldBe(1); - creators.Count(c => c.GetType() == typeof(DelegateInvokingLoadBalancerCreator)).ShouldBe(1); - } - - private void ThenTheAggregatorsAreTransient() - { - var aggregators = _serviceProvider.GetServices().ToList(); - var first = aggregators[0]; - aggregators = _serviceProvider.GetServices().ToList(); - var second = aggregators[0]; - first.ShouldNotBe(second); - } - - private void ThenTheAggregatorsAreSingleton() - { - var aggregators = _serviceProvider.GetServices().ToList(); - var first = aggregators[0]; - aggregators = _serviceProvider.GetServices().ToList(); - var second = aggregators[0]; - first.ShouldBe(second); - } - - private void ThenAnOcelotBuilderIsReturned() - { - _ocelotBuilder.ShouldBeOfType(); - } - - private void ThenTheIPlaceholderInstanceIsReplaced() - { - _serviceProvider = _services.BuildServiceProvider(); - var placeholders = _serviceProvider.GetService(); - placeholders.ShouldBeOfType(); - } - - private void WhenISetUpOcelotServices() - { - try - { - _ocelotBuilder = _services.AddOcelot(_configRoot); - } - catch (Exception e) - { - _ex = e; - } - } - - private void WhenISetUpOcelotServicesWithoutConfig() - { - try - { - _ocelotBuilder = _services.AddOcelot(); - } - catch (Exception e) - { - _ex = e; - } - } - - private void WhenIAccessLoggerFactory() - { - try - { - _serviceProvider = _services.BuildServiceProvider(); - var logger = _serviceProvider.GetService(); - logger.ShouldNotBeNull(); - } - catch (Exception e) - { - _ex = e; - } - } - - private void WhenIValidateScopes() - { - try - { - _serviceProvider = _services.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = true }); - } - catch (Exception e) - { - _ex = e; - } - } - - private void ThenAnExceptionIsntThrown() - { - _ex.ShouldBeNull(); - } - - private class FakeCustomLoadBalancer : ILoadBalancer - { - public Task> Lease(DownstreamContext context) - { - // Not relevant for these tests - throw new NotImplementedException(); - } - - public void Release(ServiceHostAndPort hostAndPort) - { - // Not relevant for these tests - throw new NotImplementedException(); - } - } - } -} + public class OcelotBuilderTests + { + private readonly IServiceCollection _services; + private IServiceProvider _serviceProvider; + private readonly IConfiguration _configRoot; + private IOcelotBuilder _ocelotBuilder; + private readonly int _maxRetries; + private Exception _ex; + + public OcelotBuilderTests() + { + _configRoot = new ConfigurationRoot(new List()); + _services = new ServiceCollection(); + _services.AddSingleton(GetHostingEnvironment()); + _services.AddSingleton(_configRoot); + _maxRetries = 100; + } + + private IWebHostEnvironment GetHostingEnvironment() + { + var environment = new Mock(); + environment + .Setup(e => e.ApplicationName) + .Returns(typeof(OcelotBuilderTests).GetTypeInfo().Assembly.GetName().Name); + + return environment.Object; + } + + [Fact] + public void should_add_specific_delegating_handlers_transient() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => AddSpecificTransientDelegatingHandler()) + .And(x => AddSpecificTransientDelegatingHandler()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificHandlers()) + .And(x => ThenTheSpecificHandlersAreTransient()) + .BDDfy(); + } + + [Fact] + public void should_add_type_specific_delegating_handlers_transient() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => AddTypeSpecificTransientDelegatingHandler(typeof(FakeDelegatingHandler))) + .And(x => AddTypeSpecificTransientDelegatingHandler(typeof(FakeDelegatingHandlerTwo))) + .Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificHandlers()) + .And(x => ThenTheSpecificHandlersAreTransient()) + .BDDfy(); + } + + [Fact] + public void should_add_global_delegating_handlers_transient() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => AddTransientGlobalDelegatingHandler()) + .And(x => AddTransientGlobalDelegatingHandler()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsHandlers()) + .And(x => ThenTheGlobalHandlersAreTransient()) + .BDDfy(); + } + + [Fact] + public void should_add_global_type_delegating_handlers_transient() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => AddTransientGlobalDelegatingHandler()) + .And(x => AddTransientGlobalDelegatingHandler()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsHandlers()) + .And(x => ThenTheGlobalHandlersAreTransient()) + .BDDfy(); + } + + [Fact] + public void should_set_up_services() + { + this.When(x => WhenISetUpOcelotServices()) + .Then(x => ThenAnExceptionIsntThrown()) + .BDDfy(); + } + + [Fact] + public void should_return_ocelot_builder() + { + this.When(x => WhenISetUpOcelotServices()) + .Then(x => ThenAnOcelotBuilderIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_use_logger_factory() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => WhenIValidateScopes()) + .When(x => WhenIAccessLoggerFactory()) + .Then(x => ThenAnExceptionIsntThrown()) + .BDDfy(); + } + + [Fact] + public void should_set_up_without_passing_in_config() + { + this.When(x => WhenISetUpOcelotServicesWithoutConfig()) + .Then(x => ThenAnExceptionIsntThrown()) + .BDDfy(); + } + + [Fact] + public void should_add_singleton_defined_aggregators() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => AddSingletonDefinedAggregator()) + .When(x => AddSingletonDefinedAggregator()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificAggregators()) + .And(x => ThenTheAggregatorsAreSingleton()) + .BDDfy(); + } + + [Fact] + public void should_add_transient_defined_aggregators() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => AddTransientDefinedAggregator()) + .When(x => AddTransientDefinedAggregator()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsSpecificAggregators()) + .And(x => ThenTheAggregatorsAreTransient()) + .BDDfy(); + } + + [Fact] + public void should_add_custom_load_balancer_creators_by_default_ctor() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => _ocelotBuilder.AddCustomLoadBalancer()) + .Then(x => ThenTheProviderIsRegisteredAndReturnsBothBuiltInAndCustomLoadBalancerCreators()) + .BDDfy(); + } + + [Fact] + public void should_add_custom_load_balancer_creators_by_factory_method() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => _ocelotBuilder.AddCustomLoadBalancer(() => new FakeCustomLoadBalancer())) + .Then(x => ThenTheProviderIsRegisteredAndReturnsBothBuiltInAndCustomLoadBalancerCreators()) + .BDDfy(); + } + + [Fact] + public void should_add_custom_load_balancer_creators_by_di_factory_method() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => _ocelotBuilder.AddCustomLoadBalancer(provider => new FakeCustomLoadBalancer())) + .Then(x => ThenTheProviderIsRegisteredAndReturnsBothBuiltInAndCustomLoadBalancerCreators()) + .BDDfy(); + } + + [Fact] + public void should_add_custom_load_balancer_creators_by_factory_method_with_arguments() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => _ocelotBuilder.AddCustomLoadBalancer((reroute, discoveryProvider) => new FakeCustomLoadBalancer())) + .Then(x => ThenTheProviderIsRegisteredAndReturnsBothBuiltInAndCustomLoadBalancerCreators()) + .BDDfy(); + } + + [Fact] + public void should_replace_iplaceholder() + { + this.Given(x => x.WhenISetUpOcelotServices()) + .When(x => AddConfigPlaceholders()) + .Then(x => ThenAnExceptionIsntThrown()) + .And(x => ThenTheIPlaceholderInstanceIsReplaced()) + .BDDfy(); + } + + [Fact] + public void should_add_custom_load_balancer_creators() + { + this.Given(x => WhenISetUpOcelotServices()) + .When(x => _ocelotBuilder.AddCustomLoadBalancer((provider, reroute, discoveryProvider) => new FakeCustomLoadBalancer())) + .Then(x => ThenTheProviderIsRegisteredAndReturnsBothBuiltInAndCustomLoadBalancerCreators()) + .BDDfy(); + } + + private void AddSingletonDefinedAggregator() + where T : class, IDefinedAggregator + { + _ocelotBuilder.AddSingletonDefinedAggregator(); + } + + private void AddTransientDefinedAggregator() + where T : class, IDefinedAggregator + { + _ocelotBuilder.AddTransientDefinedAggregator(); + } + + private void AddConfigPlaceholders() + { + _ocelotBuilder.AddConfigPlaceholders(); + } + + private void ThenTheSpecificHandlersAreTransient() + { + var handlers = _serviceProvider.GetServices().ToList(); + var first = handlers[0]; + handlers = _serviceProvider.GetServices().ToList(); + var second = handlers[0]; + first.ShouldNotBe(second); + } + + private void ThenTheGlobalHandlersAreTransient() + { + var handlers = _serviceProvider.GetServices().ToList(); + var first = handlers[0].DelegatingHandler; + handlers = _serviceProvider.GetServices().ToList(); + var second = handlers[0].DelegatingHandler; + first.ShouldNotBe(second); + } + + private void AddTransientGlobalDelegatingHandler() + where T : DelegatingHandler + { + _ocelotBuilder.AddDelegatingHandler(true); + } + + private void AddSpecificTransientDelegatingHandler() + where T : DelegatingHandler + { + _ocelotBuilder.AddDelegatingHandler(); + } + + private void AddTypeTransientGlobalDelegatingHandler(Type type) + { + _ocelotBuilder.AddDelegatingHandler(type, true); + } + + private void AddTypeSpecificTransientDelegatingHandler(Type type) + { + _ocelotBuilder.AddDelegatingHandler(type); + } + + private void ThenTheProviderIsRegisteredAndReturnsHandlers() + { + _serviceProvider = _services.BuildServiceProvider(); + var handlers = _serviceProvider.GetServices().ToList(); + handlers[0].DelegatingHandler.ShouldBeOfType(); + handlers[1].DelegatingHandler.ShouldBeOfType(); + } + + private void ThenTheProviderIsRegisteredAndReturnsSpecificHandlers() + { + _serviceProvider = _services.BuildServiceProvider(); + var handlers = _serviceProvider.GetServices().ToList(); + handlers[0].ShouldBeOfType(); + handlers[1].ShouldBeOfType(); + } + + private void ThenTheProviderIsRegisteredAndReturnsSpecificAggregators() + { + _serviceProvider = _services.BuildServiceProvider(); + var handlers = _serviceProvider.GetServices().ToList(); + handlers[0].ShouldBeOfType(); + handlers[1].ShouldBeOfType(); + } + + private void ThenTheProviderIsRegisteredAndReturnsBothBuiltInAndCustomLoadBalancerCreators() + { + _serviceProvider = _services.BuildServiceProvider(); + var creators = _serviceProvider.GetServices().ToList(); + creators.Count(c => c.GetType() == typeof(NoLoadBalancerCreator)).ShouldBe(1); + creators.Count(c => c.GetType() == typeof(RoundRobinCreator)).ShouldBe(1); + creators.Count(c => c.GetType() == typeof(CookieStickySessionsCreator)).ShouldBe(1); + creators.Count(c => c.GetType() == typeof(LeastConnectionCreator)).ShouldBe(1); + creators.Count(c => c.GetType() == typeof(DelegateInvokingLoadBalancerCreator)).ShouldBe(1); + } + + private void ThenTheAggregatorsAreTransient() + { + var aggregators = _serviceProvider.GetServices().ToList(); + var first = aggregators[0]; + aggregators = _serviceProvider.GetServices().ToList(); + var second = aggregators[0]; + first.ShouldNotBe(second); + } + + private void ThenTheAggregatorsAreSingleton() + { + var aggregators = _serviceProvider.GetServices().ToList(); + var first = aggregators[0]; + aggregators = _serviceProvider.GetServices().ToList(); + var second = aggregators[0]; + first.ShouldBe(second); + } + + private void ThenAnOcelotBuilderIsReturned() + { + _ocelotBuilder.ShouldBeOfType(); + } + + private void ThenTheIPlaceholderInstanceIsReplaced() + { + _serviceProvider = _services.BuildServiceProvider(); + var placeholders = _serviceProvider.GetService(); + placeholders.ShouldBeOfType(); + } + + private void WhenISetUpOcelotServices() + { + try + { + _ocelotBuilder = _services.AddOcelot(_configRoot); + } + catch (Exception e) + { + _ex = e; + } + } + + private void WhenISetUpOcelotServicesWithoutConfig() + { + try + { + _ocelotBuilder = _services.AddOcelot(); + } + catch (Exception e) + { + _ex = e; + } + } + + private void WhenIAccessLoggerFactory() + { + try + { + _serviceProvider = _services.BuildServiceProvider(); + var logger = _serviceProvider.GetService(); + logger.ShouldNotBeNull(); + } + catch (Exception e) + { + _ex = e; + } + } + + private void WhenIValidateScopes() + { + try + { + _serviceProvider = _services.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = true }); + } + catch (Exception e) + { + _ex = e; + } + } + + private void ThenAnExceptionIsntThrown() + { + _ex.ShouldBeNull(); + } + + private class FakeCustomLoadBalancer : ILoadBalancer + { + public Task> Lease(HttpContext httpContext) + { + // Not relevant for these tests + throw new NotImplementedException(); + } + + public void Release(ServiceHostAndPort hostAndPort) + { + // Not relevant for these tests + throw new NotImplementedException(); + } + } + } +} diff --git a/test/Ocelot.UnitTests/DownstreamPathManipulation/ClaimsToDownstreamPathMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamPathManipulation/ClaimsToDownstreamPathMiddlewareTests.cs index 80003300..f82cc565 100644 --- a/test/Ocelot.UnitTests/DownstreamPathManipulation/ClaimsToDownstreamPathMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamPathManipulation/ClaimsToDownstreamPathMiddlewareTests.cs @@ -1,101 +1,104 @@ -using Microsoft.AspNetCore.Http; -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.DownstreamRouteFinder; -using Ocelot.DownstreamRouteFinder.UrlMatcher; -using Ocelot.Logging; -using Ocelot.Middleware; -using Ocelot.PathManipulation; -using Ocelot.PathManipulation.Middleware; -using Ocelot.Request.Middleware; -using Ocelot.Responses; -using Ocelot.Values; -using System.Collections.Generic; -using System.Net.Http; -using System.Security.Claims; -using System.Threading.Tasks; -using TestStack.BDDfy; -using Xunit; +using Microsoft.AspNetCore.Http; +namespace Ocelot.UnitTests.DownstreamPathManipulation +{ + using Ocelot.DownstreamPathManipulation.Middleware; + using Ocelot.Infrastructure.RequestData; + using Moq; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Logging; + using Ocelot.Middleware; + using Ocelot.PathManipulation; + using Ocelot.Request.Middleware; + using Ocelot.Responses; + using Ocelot.Values; + using System.Collections.Generic; + using System.Net.Http; + using System.Security.Claims; + using System.Threading.Tasks; + using TestStack.BDDfy; + using Xunit; + using Ocelot.DownstreamRouteFinder.Middleware; -namespace Ocelot.UnitTests.DownstreamPathManipulation -{ - public class ClaimsToDownstreamPathMiddlewareTests - { - private readonly Mock _changePath; - private Mock _loggerFactory; - private Mock _logger; - private ClaimsToDownstreamPathMiddleware _middleware; - private DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; - - public ClaimsToDownstreamPathMiddlewareTests() - { - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - _loggerFactory = new Mock(); - _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = context => Task.CompletedTask; + public class ClaimsToDownstreamPathMiddlewareTests + { + private readonly Mock _changePath; + private Mock _loggerFactory; + private Mock _logger; + private ClaimsToDownstreamPathMiddleware _middleware; + private RequestDelegate _next; + private HttpContext _httpContext; + + public ClaimsToDownstreamPathMiddlewareTests() + { + _httpContext = new DefaultHttpContext(); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = context => Task.CompletedTask; _changePath = new Mock(); - _downstreamContext.DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); - _middleware = new ClaimsToDownstreamPathMiddleware(_next, _loggerFactory.Object, _changePath.Object); - } + _httpContext.Items.UpsertDownstreamRequest(new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com"))); + _middleware = new ClaimsToDownstreamPathMiddleware(_next, _loggerFactory.Object, _changePath.Object); + } + + [Fact] + public void should_call_add_queries_correctly() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithClaimsToDownstreamPath(new List + { + new ClaimToThing("UserId", "Subject", "", 0), + }) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => x.GivenTheChangeDownstreamPathReturnsOk()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenChangeDownstreamPathIsCalledCorrectly()) + .BDDfy(); + + } + + private void WhenICallTheMiddleware() + { + _middleware.Invoke(_httpContext).GetAwaiter().GetResult(); + } + + private void GivenTheChangeDownstreamPathReturnsOk() + { + _changePath + .Setup(x => x.ChangeDownstreamPath( + It.IsAny>(), + It.IsAny>(), + It.IsAny(), + It.IsAny>())) + .Returns(new OkResponse()); + } + + private void ThenChangeDownstreamPathIsCalledCorrectly() + { + _changePath + .Verify(x => x.ChangeDownstreamPath( + It.IsAny>(), + It.IsAny>(), + _httpContext.Items.DownstreamReRoute().DownstreamPathTemplate, + _httpContext.Items.TemplatePlaceholderNameAndValues()), Times.Once); + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + { + _httpContext.Items.UpsertTemplatePlaceholderNameAndValues(downstreamRoute.TemplatePlaceholderNameAndValues); - [Fact] - public void should_call_add_queries_correctly() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithClaimsToDownstreamPath(new List - { - new ClaimToThing("UserId", "Subject", "", 0), - }) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => x.GivenTheChangeDownstreamPathReturnsOk()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenChangeDownstreamPathIsCalledCorrectly()) - .BDDfy(); - - } - - private void WhenICallTheMiddleware() - { - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - } - - private void GivenTheChangeDownstreamPathReturnsOk() - { - _changePath - .Setup(x => x.ChangeDownstreamPath( - It.IsAny>(), - It.IsAny>(), - It.IsAny(), - It.IsAny>())) - .Returns(new OkResponse()); - } - - private void ThenChangeDownstreamPathIsCalledCorrectly() - { - _changePath - .Verify(x => x.ChangeDownstreamPath( - It.IsAny>(), - It.IsAny>(), - _downstreamContext.DownstreamReRoute.DownstreamPathTemplate, - _downstreamContext.TemplatePlaceholderNameAndValues), Times.Once); - } - - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues; - _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; - } - - } -} + _httpContext.Items.UpsertDownstreamReRoute(downstreamRoute.ReRoute.DownstreamReRoute[0]); + } + + } +} diff --git a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs index 12e0a804..d27a65a5 100644 --- a/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamRouteFinder/DownstreamRouteFinderMiddlewareTests.cs @@ -11,11 +11,12 @@ using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Logging; using Ocelot.Middleware; - using Ocelot.Middleware.Multiplexer; + using Ocelot.Multiplexer; using Ocelot.Responses; using Shouldly; using System.Collections.Generic; using System.Threading.Tasks; + using Ocelot.Infrastructure.RequestData; using TestStack.BDDfy; using Xunit; @@ -28,22 +29,20 @@ private Mock _loggerFactory; private Mock _logger; private readonly DownstreamRouteFinderMiddleware _middleware; - private readonly DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; - private readonly Mock _multiplexer; + private RequestDelegate _next; + private HttpContext _httpContext; public DownstreamRouteFinderMiddlewareTests() { + _httpContext = new DefaultHttpContext(); _finder = new Mock(); _factory = new Mock(); _factory.Setup(x => x.Get(It.IsAny())).Returns(_finder.Object); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); _loggerFactory = new Mock(); _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _next = context => Task.CompletedTask; - _multiplexer = new Mock(); - _middleware = new DownstreamRouteFinderMiddleware(_next, _loggerFactory.Object, _factory.Object, _multiplexer.Object); + _middleware = new DownstreamRouteFinderMiddleware(_next, _loggerFactory.Object, _factory.Object); } [Fact] @@ -71,13 +70,13 @@ private void WhenICallTheMiddleware() { - _middleware.Invoke(_downstreamContext).GetAwaiter().GetType(); + _middleware.Invoke(_httpContext).GetAwaiter().GetType(); } private void GivenTheFollowingConfig(IInternalConfiguration config) { _config = config; - _downstreamContext.Configuration = config; + _httpContext.Items.SetIInternalConfiguration(config); } private void GivenTheDownStreamRouteFinderReturns(DownstreamRoute downstreamRoute) @@ -90,8 +89,8 @@ private void ThenTheScopedDataRepositoryIsCalledCorrectly() { - _downstreamContext.TemplatePlaceholderNameAndValues.ShouldBe(_downstreamRoute.Data.TemplatePlaceholderNameAndValues); - _downstreamContext.Configuration.ServiceProviderConfiguration.ShouldBe(_config.ServiceProviderConfiguration); + _httpContext.Items.TemplatePlaceholderNameAndValues().ShouldBe(_downstreamRoute.Data.TemplatePlaceholderNameAndValues); + _httpContext.Items.IInternalConfiguration().ServiceProviderConfiguration.ShouldBe(_config.ServiceProviderConfiguration); } } } diff --git a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs index f6e0a2d5..0cf8ff44 100644 --- a/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/DownstreamUrlCreator/DownstreamUrlCreatorMiddlewareTests.cs @@ -1,436 +1,440 @@ -namespace Ocelot.UnitTests.DownstreamUrlCreator -{ - using Microsoft.AspNetCore.Http; - using Moq; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using Ocelot.DownstreamRouteFinder; - using Ocelot.DownstreamRouteFinder.UrlMatcher; - using Ocelot.DownstreamUrlCreator.Middleware; - using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; - using Ocelot.Logging; - using Ocelot.Middleware; - using Ocelot.Request.Middleware; - using Ocelot.Responses; - using Ocelot.Values; - using Shouldly; - using System; - using System.Collections.Generic; - using System.Net.Http; - using System.Threading.Tasks; - using TestStack.BDDfy; +namespace Ocelot.UnitTests.DownstreamUrlCreator +{ + using Microsoft.AspNetCore.Http; + using Moq; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.DownstreamUrlCreator.Middleware; + using Ocelot.DownstreamUrlCreator.UrlTemplateReplacer; + using Ocelot.Logging; + using Ocelot.Middleware; + using Ocelot.Request.Middleware; + using Ocelot.Responses; + using Ocelot.Values; + using Shouldly; + using System; + using System.Collections.Generic; + using System.Net.Http; + using System.Threading.Tasks; + using Ocelot.Infrastructure.RequestData; + using TestStack.BDDfy; using Xunit; + using Ocelot.DownstreamRouteFinder.Middleware; - public class DownstreamUrlCreatorMiddlewareTests - { - private readonly Mock _downstreamUrlTemplateVariableReplacer; - private OkResponse _downstreamPath; - private readonly Mock _loggerFactory; - private Mock _logger; - private DownstreamUrlCreatorMiddleware _middleware; - private readonly DownstreamContext _downstreamContext; - private readonly OcelotRequestDelegate _next; - private readonly HttpRequestMessage _request; - - public DownstreamUrlCreatorMiddlewareTests() - { - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - _loggerFactory = new Mock(); - _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _downstreamUrlTemplateVariableReplacer = new Mock(); + public class DownstreamUrlCreatorMiddlewareTests + { + private readonly Mock _downstreamUrlTemplateVariableReplacer; + private OkResponse _downstreamPath; + private readonly Mock _loggerFactory; + private Mock _logger; + private DownstreamUrlCreatorMiddleware _middleware; + private readonly RequestDelegate _next; + private readonly HttpRequestMessage _request; + private HttpContext _httpContext; + private Mock _repo; + + public DownstreamUrlCreatorMiddlewareTests() + { + _repo = new Mock(); + _httpContext = new DefaultHttpContext(); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _downstreamUrlTemplateVariableReplacer = new Mock(); _request = new HttpRequestMessage(HttpMethod.Get, "https://my.url/abc/?q=123"); - _downstreamContext.DownstreamRequest = new DownstreamRequest(_request); - _next = context => Task.CompletedTask; - } - - [Fact] - public void should_replace_scheme_and_path() - { - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithDownstreamScheme("https") - .Build(); - - var config = new ServiceProviderConfigurationBuilder() - .Build(); - - this.Given(x => x.GivenTheDownStreamRouteIs( - new DownstreamRoute( - new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()))) - .And(x => x.GivenTheDownstreamRequestUriIs("http://my.url/abc?q=123")) - .And(x => GivenTheServiceProviderConfigIs(config)) - .And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1")) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheDownstreamRequestUriIs("https://my.url:80/api/products/1?q=123")) - .And(x => ThenTheQueryStringIs("?q=123")) - .BDDfy(); - } - - [Fact] - public void should_replace_query_string() - { - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("/api/units/{subscriptionId}/{unitId}/updates") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithDownstreamScheme("https") - .Build(); - - var config = new ServiceProviderConfigurationBuilder() - .Build(); - - this.Given(x => x.GivenTheDownStreamRouteIs( - new DownstreamRoute( - new List - { - new PlaceholderNameAndValue("{subscriptionId}", "1"), - new PlaceholderNameAndValue("{unitId}", "2") - }, - new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()))) - .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:5000/api/subscriptions/1/updates?unitId=2")) - .And(x => GivenTheServiceProviderConfigIs(config)) - .And(x => x.GivenTheUrlReplacerWillReturn("api/units/1/2/updates")) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheDownstreamRequestUriIs("https://localhost:5000/api/units/1/2/updates")) - .And(x => ThenTheQueryStringIs("")) - .BDDfy(); - } - - [Fact] - public void should_replace_query_string_but_leave_non_placeholder_queries() - { - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("/api/units/{subscriptionId}/{unitId}/updates") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithDownstreamScheme("https") - .Build(); - - var config = new ServiceProviderConfigurationBuilder() - .Build(); - - this.Given(x => x.GivenTheDownStreamRouteIs( - new DownstreamRoute( - new List - { - new PlaceholderNameAndValue("{subscriptionId}", "1"), - new PlaceholderNameAndValue("{unitId}", "2") - }, - new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()))) - .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:5000/api/subscriptions/1/updates?unitId=2&productId=2")) - .And(x => GivenTheServiceProviderConfigIs(config)) - .And(x => x.GivenTheUrlReplacerWillReturn("api/units/1/2/updates")) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheDownstreamRequestUriIs("https://localhost:5000/api/units/1/2/updates?productId=2")) - .And(x => ThenTheQueryStringIs("?productId=2")) - .BDDfy(); - } - - [Fact] - public void should_replace_query_string_exact_match() - { - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("/api/units/{subscriptionId}/{unitId}/updates/{unitIdIty}") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithDownstreamScheme("https") - .Build(); - - var config = new ServiceProviderConfigurationBuilder() - .Build(); - - this.Given(x => x.GivenTheDownStreamRouteIs( - new DownstreamRoute( - new List - { - new PlaceholderNameAndValue("{subscriptionId}", "1"), - new PlaceholderNameAndValue("{unitId}", "2"), - new PlaceholderNameAndValue("{unitIdIty}", "3") - }, - new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()))) - .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:5000/api/subscriptions/1/updates?unitId=2?unitIdIty=3")) - .And(x => GivenTheServiceProviderConfigIs(config)) - .And(x => x.GivenTheUrlReplacerWillReturn("api/units/1/2/updates/3")) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheDownstreamRequestUriIs("https://localhost:5000/api/units/1/2/updates/3")) - .And(x => ThenTheQueryStringIs("")) - .BDDfy(); - } - - [Fact] - public void should_not_create_service_fabric_url() - { - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithUpstreamHttpMethod(new List { "Get" }) - .WithDownstreamScheme("https") - .Build(); - - var config = new ServiceProviderConfigurationBuilder() - .WithType("ServiceFabric") - .WithHost("localhost") - .WithPort(19081) - .Build(); - - this.Given(x => x.GivenTheDownStreamRouteIs( - new DownstreamRoute( - new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()))) - .And(x => x.GivenTheDownstreamRequestUriIs("http://my.url/abc?q=123")) - .And(x => GivenTheServiceProviderConfigIs(config)) - .And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1")) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheDownstreamRequestUriIs("https://my.url:80/api/products/1?q=123")) - .BDDfy(); - } - - [Fact] - public void should_create_service_fabric_url() - { - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithDownstreamScheme("http") - .WithServiceName("Ocelot/OcelotApp") - .WithUseServiceDiscovery(true) - .Build(); - - var downstreamRoute = new DownstreamRoute( - new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .Build()); - - var config = new ServiceProviderConfigurationBuilder() - .WithType("ServiceFabric") - .WithHost("localhost") - .WithPort(19081) - .Build(); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => GivenTheServiceProviderConfigIs(config)) - .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:19081")) - .And(x => x.GivenTheUrlReplacerWillReturnSequence("/api/products/1", "Ocelot/OcelotApp")) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheDownstreamRequestUriIs("http://localhost:19081/Ocelot/OcelotApp/api/products/1")) - .BDDfy(); - } - - [Fact] - public void should_create_service_fabric_url_with_query_string_for_stateless_service() - { - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithDownstreamScheme("http") - .WithServiceName("Ocelot/OcelotApp") - .WithUseServiceDiscovery(true) - .Build(); - - var downstreamRoute = new DownstreamRoute( - new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .Build()); - - var config = new ServiceProviderConfigurationBuilder() - .WithType("ServiceFabric") - .WithHost("localhost") - .WithPort(19081) - .Build(); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => GivenTheServiceProviderConfigIs(config)) - .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:19081?Tom=test&laura=1")) - .And(x => x.GivenTheUrlReplacerWillReturnSequence("/api/products/1", "Ocelot/OcelotApp")) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheDownstreamRequestUriIs("http://localhost:19081/Ocelot/OcelotApp/api/products/1?Tom=test&laura=1")) - .BDDfy(); - } - - [Fact] - public void should_create_service_fabric_url_with_query_string_for_stateful_service() - { - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithDownstreamScheme("http") - .WithServiceName("Ocelot/OcelotApp") - .WithUseServiceDiscovery(true) - .Build(); - - var downstreamRoute = new DownstreamRoute( - new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .Build()); - - var config = new ServiceProviderConfigurationBuilder() - .WithType("ServiceFabric") - .WithHost("localhost") - .WithPort(19081) - .Build(); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => GivenTheServiceProviderConfigIs(config)) - .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:19081?PartitionKind=test&PartitionKey=1")) - .And(x => x.GivenTheUrlReplacerWillReturnSequence("/api/products/1", "Ocelot/OcelotApp")) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheDownstreamRequestUriIs("http://localhost:19081/Ocelot/OcelotApp/api/products/1?PartitionKind=test&PartitionKey=1")) - .BDDfy(); - } - - [Fact] - public void should_create_service_fabric_url_with_version_from_upstream_path_template() - { - var downstreamRoute = new DownstreamRoute( - new List(), - new ReRouteBuilder().WithDownstreamReRoute( - new DownstreamReRouteBuilder() - .WithDownstreamScheme("http") - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("/products").Build()) - .WithUseServiceDiscovery(true) - .Build() - ).Build()); - - var config = new ServiceProviderConfigurationBuilder() - .WithType("ServiceFabric") - .WithHost("localhost") - .WithPort(19081) - .Build(); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => GivenTheServiceProviderConfigIs(config)) - .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:19081?PartitionKind=test&PartitionKey=1")) - .And(x => x.GivenTheUrlReplacerWillReturnSequence("/products", "Service_1.0/Api")) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheDownstreamRequestUriIs("http://localhost:19081/Service_1.0/Api/products?PartitionKind=test&PartitionKey=1")) - .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(new UpstreamPathTemplateBuilder().WithOriginalValue("/uc/Authorized/{server}/{action}").Build()) - .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(); - } - - [Fact] - public void should_not_replace_by_empty_scheme() - { - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithDownstreamScheme("") - .WithServiceName("Ocelot/OcelotApp") - .WithUseServiceDiscovery(true) - .Build(); - - var downstreamRoute = new DownstreamRoute( - new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .Build()); - - var config = new ServiceProviderConfigurationBuilder() - .WithType("ServiceFabric") - .WithHost("localhost") - .WithPort(19081) - .Build(); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => GivenTheServiceProviderConfigIs(config)) - .And(x => x.GivenTheDownstreamRequestUriIs("https://localhost:19081?PartitionKind=test&PartitionKey=1")) - .And(x => x.GivenTheUrlReplacerWillReturnSequence("/api/products/1", "Ocelot/OcelotApp")) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheDownstreamRequestUriIs("https://localhost:19081/Ocelot/OcelotApp/api/products/1?PartitionKind=test&PartitionKey=1")) - .BDDfy(); - } - - private void GivenTheServiceProviderConfigIs(ServiceProviderConfiguration config) - { + _next = context => Task.CompletedTask; + } + + [Fact] + public void should_replace_scheme_and_path() + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithDownstreamScheme("https") + .Build(); + + var config = new ServiceProviderConfigurationBuilder() + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs( + new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .And(x => x.GivenTheDownstreamRequestUriIs("http://my.url/abc?q=123")) + .And(x => GivenTheServiceProviderConfigIs(config)) + .And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1")) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamRequestUriIs("https://my.url:80/api/products/1?q=123")) + .And(x => ThenTheQueryStringIs("?q=123")) + .BDDfy(); + } + + [Fact] + public void should_replace_query_string() + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("/api/units/{subscriptionId}/{unitId}/updates") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithDownstreamScheme("https") + .Build(); + + var config = new ServiceProviderConfigurationBuilder() + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs( + new DownstreamRoute( + new List + { + new PlaceholderNameAndValue("{subscriptionId}", "1"), + new PlaceholderNameAndValue("{unitId}", "2") + }, + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:5000/api/subscriptions/1/updates?unitId=2")) + .And(x => GivenTheServiceProviderConfigIs(config)) + .And(x => x.GivenTheUrlReplacerWillReturn("api/units/1/2/updates")) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamRequestUriIs("https://localhost:5000/api/units/1/2/updates")) + .And(x => ThenTheQueryStringIs("")) + .BDDfy(); + } + + [Fact] + public void should_replace_query_string_but_leave_non_placeholder_queries() + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("/api/units/{subscriptionId}/{unitId}/updates") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithDownstreamScheme("https") + .Build(); + + var config = new ServiceProviderConfigurationBuilder() + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs( + new DownstreamRoute( + new List + { + new PlaceholderNameAndValue("{subscriptionId}", "1"), + new PlaceholderNameAndValue("{unitId}", "2") + }, + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:5000/api/subscriptions/1/updates?unitId=2&productId=2")) + .And(x => GivenTheServiceProviderConfigIs(config)) + .And(x => x.GivenTheUrlReplacerWillReturn("api/units/1/2/updates")) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamRequestUriIs("https://localhost:5000/api/units/1/2/updates?productId=2")) + .And(x => ThenTheQueryStringIs("?productId=2")) + .BDDfy(); + } + + [Fact] + public void should_replace_query_string_exact_match() + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("/api/units/{subscriptionId}/{unitId}/updates/{unitIdIty}") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithDownstreamScheme("https") + .Build(); + + var config = new ServiceProviderConfigurationBuilder() + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs( + new DownstreamRoute( + new List + { + new PlaceholderNameAndValue("{subscriptionId}", "1"), + new PlaceholderNameAndValue("{unitId}", "2"), + new PlaceholderNameAndValue("{unitIdIty}", "3") + }, + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:5000/api/subscriptions/1/updates?unitId=2?unitIdIty=3")) + .And(x => GivenTheServiceProviderConfigIs(config)) + .And(x => x.GivenTheUrlReplacerWillReturn("api/units/1/2/updates/3")) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamRequestUriIs("https://localhost:5000/api/units/1/2/updates/3")) + .And(x => ThenTheQueryStringIs("")) + .BDDfy(); + } + + [Fact] + public void should_not_create_service_fabric_url() + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithUpstreamHttpMethod(new List { "Get" }) + .WithDownstreamScheme("https") + .Build(); + + var config = new ServiceProviderConfigurationBuilder() + .WithType("ServiceFabric") + .WithHost("localhost") + .WithPort(19081) + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs( + new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()))) + .And(x => x.GivenTheDownstreamRequestUriIs("http://my.url/abc?q=123")) + .And(x => GivenTheServiceProviderConfigIs(config)) + .And(x => x.GivenTheUrlReplacerWillReturn("/api/products/1")) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamRequestUriIs("https://my.url:80/api/products/1?q=123")) + .BDDfy(); + } + + [Fact] + public void should_create_service_fabric_url() + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamScheme("http") + .WithServiceName("Ocelot/OcelotApp") + .WithUseServiceDiscovery(true) + .Build(); + + var downstreamRoute = new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .Build()); + + var config = new ServiceProviderConfigurationBuilder() + .WithType("ServiceFabric") + .WithHost("localhost") + .WithPort(19081) + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => GivenTheServiceProviderConfigIs(config)) + .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:19081")) + .And(x => x.GivenTheUrlReplacerWillReturnSequence("/api/products/1", "Ocelot/OcelotApp")) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamRequestUriIs("http://localhost:19081/Ocelot/OcelotApp/api/products/1")) + .BDDfy(); + } + + [Fact] + public void should_create_service_fabric_url_with_query_string_for_stateless_service() + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamScheme("http") + .WithServiceName("Ocelot/OcelotApp") + .WithUseServiceDiscovery(true) + .Build(); + + var downstreamRoute = new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .Build()); + + var config = new ServiceProviderConfigurationBuilder() + .WithType("ServiceFabric") + .WithHost("localhost") + .WithPort(19081) + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => GivenTheServiceProviderConfigIs(config)) + .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:19081?Tom=test&laura=1")) + .And(x => x.GivenTheUrlReplacerWillReturnSequence("/api/products/1", "Ocelot/OcelotApp")) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamRequestUriIs("http://localhost:19081/Ocelot/OcelotApp/api/products/1?Tom=test&laura=1")) + .BDDfy(); + } + + [Fact] + public void should_create_service_fabric_url_with_query_string_for_stateful_service() + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamScheme("http") + .WithServiceName("Ocelot/OcelotApp") + .WithUseServiceDiscovery(true) + .Build(); + + var downstreamRoute = new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .Build()); + + var config = new ServiceProviderConfigurationBuilder() + .WithType("ServiceFabric") + .WithHost("localhost") + .WithPort(19081) + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => GivenTheServiceProviderConfigIs(config)) + .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:19081?PartitionKind=test&PartitionKey=1")) + .And(x => x.GivenTheUrlReplacerWillReturnSequence("/api/products/1", "Ocelot/OcelotApp")) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamRequestUriIs("http://localhost:19081/Ocelot/OcelotApp/api/products/1?PartitionKind=test&PartitionKey=1")) + .BDDfy(); + } + + [Fact] + public void should_create_service_fabric_url_with_version_from_upstream_path_template() + { + var downstreamRoute = new DownstreamRoute( + new List(), + new ReRouteBuilder().WithDownstreamReRoute( + new DownstreamReRouteBuilder() + .WithDownstreamScheme("http") + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("/products").Build()) + .WithUseServiceDiscovery(true) + .Build() + ).Build()); + + var config = new ServiceProviderConfigurationBuilder() + .WithType("ServiceFabric") + .WithHost("localhost") + .WithPort(19081) + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => GivenTheServiceProviderConfigIs(config)) + .And(x => x.GivenTheDownstreamRequestUriIs("http://localhost:19081?PartitionKind=test&PartitionKey=1")) + .And(x => x.GivenTheUrlReplacerWillReturnSequence("/products", "Service_1.0/Api")) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamRequestUriIs("http://localhost:19081/Service_1.0/Api/products?PartitionKind=test&PartitionKey=1")) + .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(new UpstreamPathTemplateBuilder().WithOriginalValue("/uc/Authorized/{server}/{action}").Build()) + .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(); + } + + [Fact] + public void should_not_replace_by_empty_scheme() + { + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithDownstreamScheme("") + .WithServiceName("Ocelot/OcelotApp") + .WithUseServiceDiscovery(true) + .Build(); + + var downstreamRoute = new DownstreamRoute( + new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .Build()); + + var config = new ServiceProviderConfigurationBuilder() + .WithType("ServiceFabric") + .WithHost("localhost") + .WithPort(19081) + .Build(); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => GivenTheServiceProviderConfigIs(config)) + .And(x => x.GivenTheDownstreamRequestUriIs("https://localhost:19081?PartitionKind=test&PartitionKey=1")) + .And(x => x.GivenTheUrlReplacerWillReturnSequence("/api/products/1", "Ocelot/OcelotApp")) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamRequestUriIs("https://localhost:19081/Ocelot/OcelotApp/api/products/1?PartitionKind=test&PartitionKey=1")) + .BDDfy(); + } + + private void GivenTheServiceProviderConfigIs(ServiceProviderConfiguration config) + { var configuration = new InternalConfiguration(null, null, config, null, null, null, null, null, null); - _downstreamContext.Configuration = configuration; - } - - private void WhenICallTheMiddleware() + _httpContext.Items.SetIInternalConfiguration(configuration); + } + + private void WhenICallTheMiddleware() + { + _middleware = new DownstreamUrlCreatorMiddleware(_next, _loggerFactory.Object, _downstreamUrlTemplateVariableReplacer.Object); + _middleware.Invoke(_httpContext).GetAwaiter().GetResult(); + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) { - _middleware = new DownstreamUrlCreatorMiddleware(_next, _loggerFactory.Object, _downstreamUrlTemplateVariableReplacer.Object); - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - } + _httpContext.Items.UpsertTemplatePlaceholderNameAndValues(downstreamRoute.TemplatePlaceholderNameAndValues); - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues; - _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; - } - - private void GivenTheDownstreamRequestUriIs(string uri) - { + _httpContext.Items.UpsertDownstreamReRoute(downstreamRoute.ReRoute.DownstreamReRoute[0]); + } + + private void GivenTheDownstreamRequestUriIs(string uri) + { _request.RequestUri = new Uri(uri); - _downstreamContext.DownstreamRequest = new DownstreamRequest(_request); - } - - private void GivenTheUrlReplacerWillReturnSequence(params string[] paths) - { - var setup = _downstreamUrlTemplateVariableReplacer - .SetupSequence(x => x.Replace(It.IsAny(), It.IsAny>())); - foreach (var path in paths) - { - var response = new OkResponse(new DownstreamPath(path)); - setup.Returns(response); - } - } - - private void GivenTheUrlReplacerWillReturn(string path) - { - _downstreamPath = new OkResponse(new DownstreamPath(path)); - _downstreamUrlTemplateVariableReplacer - .Setup(x => x.Replace(It.IsAny(), It.IsAny>())) - .Returns(_downstreamPath); - } - - private void ThenTheDownstreamRequestUriIs(string expectedUri) - { - _downstreamContext.DownstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString.ShouldBe(expectedUri); - } - - private void ThenTheQueryStringIs(string queryString) - { - _downstreamContext.DownstreamRequest.Query.ShouldBe(queryString); - } - } -} + _httpContext.Items.UpsertDownstreamRequest(new DownstreamRequest(_request)); + } + + private void GivenTheUrlReplacerWillReturnSequence(params string[] paths) + { + var setup = _downstreamUrlTemplateVariableReplacer + .SetupSequence(x => x.Replace(It.IsAny(), It.IsAny>())); + foreach (var path in paths) + { + var response = new OkResponse(new DownstreamPath(path)); + setup.Returns(response); + } + } + + private void GivenTheUrlReplacerWillReturn(string path) + { + _downstreamPath = new OkResponse(new DownstreamPath(path)); + _downstreamUrlTemplateVariableReplacer + .Setup(x => x.Replace(It.IsAny(), It.IsAny>())) + .Returns(_downstreamPath); + } + + private void ThenTheDownstreamRequestUriIs(string expectedUri) + { + _httpContext.Items.DownstreamRequest().ToHttpRequestMessage().RequestUri.OriginalString.ShouldBe(expectedUri); + } + + private void ThenTheQueryStringIs(string queryString) + { + _httpContext.Items.DownstreamRequest().Query.ShouldBe(queryString); + } + } +} diff --git a/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs b/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs index 932f40b2..a53a9454 100644 --- a/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Errors/ExceptionHandlerMiddlewareTests.cs @@ -3,14 +3,13 @@ namespace Ocelot.UnitTests.Errors using Microsoft.AspNetCore.Http; using Moq; using Ocelot.Configuration; - using Ocelot.Configuration.Repository; using Ocelot.Errors; using Ocelot.Errors.Middleware; using Ocelot.Infrastructure.RequestData; using Ocelot.Logging; - using Ocelot.Middleware; using Shouldly; using System; + using System.Collections.Generic; using System.Net; using System.Threading.Tasks; using TestStack.BDDfy; @@ -19,19 +18,17 @@ namespace Ocelot.UnitTests.Errors public class ExceptionHandlerMiddlewareTests { private 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; + private RequestDelegate _next; + private HttpContext _httpContext; public ExceptionHandlerMiddlewareTests() { - _configRepo = new Mock(); + _httpContext = new DefaultHttpContext(); _repo = new Mock(); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); _loggerFactory = new Mock(); _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); @@ -44,9 +41,10 @@ namespace Ocelot.UnitTests.Errors throw new Exception("BOOM"); } - context.HttpContext.Response.StatusCode = (int)HttpStatusCode.OK; + _httpContext.Response.StatusCode = (int)HttpStatusCode.OK; }; - _middleware = new ExceptionHandlerMiddleware(_next, _loggerFactory.Object, _configRepo.Object, _repo.Object); + + _middleware = new ExceptionHandlerMiddleware(_next, _loggerFactory.Object, _repo.Object); } [Fact] @@ -100,16 +98,6 @@ namespace Ocelot.UnitTests.Errors .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() { @@ -122,32 +110,25 @@ namespace Ocelot.UnitTests.Errors private void WhenICallTheMiddlewareWithTheRequestIdKey(string key, string value) { - _downstreamContext.HttpContext.Request.Headers.Add(key, value); - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + _httpContext.Request.Headers.Add(key, value); + //_httpContext.Setup(x => x.Request.Headers).Returns(new HeaderDictionary() { { key, value } }); + _middleware.Invoke(_httpContext).GetAwaiter().GetResult(); } private void WhenICallTheMiddleware() { - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + _middleware.Invoke(_httpContext).GetAwaiter().GetResult(); } private void GivenTheConfigThrows() { - var ex = new Exception("outer", new Exception("inner")); - _configRepo - .Setup(x => x.Get()).Throws(ex); + // this will break when we handle not having the configuratio in the items dictionary + _httpContext.Items = new Dictionary(); } 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); + _httpContext.Response.StatusCode.ShouldBe(500); } private void TheRequestIdIsSet(string key, string value) @@ -157,9 +138,7 @@ namespace Ocelot.UnitTests.Errors private void GivenTheConfigurationIs(IInternalConfiguration config) { - var response = new Responses.OkResponse(config); - _configRepo - .Setup(x => x.Get()).Returns(response); + _httpContext.Items.Add("IInternalConfiguration", config); } private void GivenAnExceptionWillNotBeThrownDownstream() @@ -174,12 +153,12 @@ namespace Ocelot.UnitTests.Errors private void ThenTheResponseIsOk() { - _downstreamContext.HttpContext.Response.StatusCode.ShouldBe(200); + _httpContext.Response.StatusCode.ShouldBe(200); } private void ThenTheResponseIsError() { - _downstreamContext.HttpContext.Response.StatusCode.ShouldBe(500); + _httpContext.Response.StatusCode.ShouldBe(500); } private void TheAspDotnetRequestIdIsSet() @@ -190,7 +169,7 @@ namespace Ocelot.UnitTests.Errors private class FakeError : Error { internal FakeError() - : base("meh", OcelotErrorCode.CannotAddDataError) + : base("meh", OcelotErrorCode.CannotAddDataError, 404) { } } diff --git a/test/Ocelot.UnitTests/Eureka/OcelotPipelineExtensionsTests.cs b/test/Ocelot.UnitTests/Eureka/OcelotPipelineExtensionsTests.cs index 340998aa..dca5ac63 100644 --- a/test/Ocelot.UnitTests/Eureka/OcelotPipelineExtensionsTests.cs +++ b/test/Ocelot.UnitTests/Eureka/OcelotPipelineExtensionsTests.cs @@ -1,59 +1,59 @@ -namespace Ocelot.UnitTests.Eureka -{ - using Microsoft.Extensions.Configuration; - using Microsoft.Extensions.DependencyInjection; - using Ocelot.DependencyInjection; - using Ocelot.Middleware; - using Ocelot.Middleware.Pipeline; - using Steeltoe.Discovery.Client; - using Shouldly; - using Steeltoe.Common.Discovery; - using Steeltoe.Discovery.Eureka; - using TestStack.BDDfy; - using Xunit; +//namespace Ocelot.UnitTests.Eureka +//{ +// using Microsoft.Extensions.Configuration; +// using Microsoft.Extensions.DependencyInjection; +// using Ocelot.DependencyInjection; +// using Ocelot.Middleware; +// using Ocelot.Middleware.Pipeline; +// using Steeltoe.Discovery.Client; +// using Shouldly; +// using Steeltoe.Common.Discovery; +// using Steeltoe.Discovery.Eureka; +// using TestStack.BDDfy; +// using Xunit; - public class OcelotPipelineExtensionsTests - { - private OcelotPipelineBuilder _builder; - private OcelotRequestDelegate _handlers; +// public class OcelotPipelineExtensionsTests +// { +// private OcelotPipelineBuilder _builder; +// private OcelotRequestDelegate _handlers; - [Fact] - public void should_set_up_pipeline() - { - this.Given(_ => GivenTheDepedenciesAreSetUp()) - .When(_ => WhenIBuild()) - .Then(_ => ThenThePipelineIsBuilt()) - .BDDfy(); - } +// [Fact] +// public void should_set_up_pipeline() +// { +// this.Given(_ => GivenTheDepedenciesAreSetUp()) +// .When(_ => WhenIBuild()) +// .Then(_ => ThenThePipelineIsBuilt()) +// .BDDfy(); +// } - private void ThenThePipelineIsBuilt() - { - _handlers.ShouldNotBeNull(); - } +// private void ThenThePipelineIsBuilt() +// { +// _handlers.ShouldNotBeNull(); +// } - private void WhenIBuild() - { - _handlers = _builder.BuildOcelotPipeline(new OcelotPipelineConfiguration()); - } +// private void WhenIBuild() +// { +// _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); - } - } -} +// 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/Headers/AddHeadersToRequestClaimToThingTests.cs b/test/Ocelot.UnitTests/Headers/AddHeadersToRequestClaimToThingTests.cs index 392712c3..f002a25a 100644 --- a/test/Ocelot.UnitTests/Headers/AddHeadersToRequestClaimToThingTests.cs +++ b/test/Ocelot.UnitTests/Headers/AddHeadersToRequestClaimToThingTests.cs @@ -150,7 +150,7 @@ namespace Ocelot.UnitTests.Headers private class AnyError : Error { public AnyError() - : base("blahh", OcelotErrorCode.UnknownError) + : base("blahh", OcelotErrorCode.UnknownError, 404) { } } diff --git a/test/Ocelot.UnitTests/Headers/ClaimsToHeadersMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/ClaimsToHeadersMiddlewareTests.cs index 20ec9b70..f7998373 100644 --- a/test/Ocelot.UnitTests/Headers/ClaimsToHeadersMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/ClaimsToHeadersMiddlewareTests.cs @@ -1,16 +1,16 @@ -using Ocelot.Middleware; - -namespace Ocelot.UnitTests.Headers +namespace Ocelot.UnitTests.Headers { using Microsoft.AspNetCore.Http; using Moq; using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.DownstreamRouteFinder.UrlMatcher; using Ocelot.Headers; using Ocelot.Headers.Middleware; using Ocelot.Logging; + using Ocelot.Middleware; using Ocelot.Request.Middleware; using Ocelot.Responses; using System.Collections.Generic; @@ -26,19 +26,19 @@ namespace Ocelot.UnitTests.Headers private Mock _loggerFactory; private Mock _logger; private ClaimsToHeadersMiddleware _middleware; - private DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; + private RequestDelegate _next; + private HttpContext _httpContext; public ClaimsToHeadersMiddlewareTests() { + _httpContext = new DefaultHttpContext(); _addHeaders = new Mock(); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); _loggerFactory = new Mock(); _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _next = context => Task.CompletedTask; _middleware = new ClaimsToHeadersMiddleware(_next, _loggerFactory.Object, _addHeaders.Object); - _downstreamContext.DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); + _httpContext.Items.UpsertDownstreamRequest(new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com"))); } [Fact] @@ -66,14 +66,16 @@ namespace Ocelot.UnitTests.Headers private void WhenICallTheMiddleware() { - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + _middleware.Invoke(_httpContext).GetAwaiter().GetResult(); } private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) { _downstreamRoute = new OkResponse(downstreamRoute); - _downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues; - _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; + + _httpContext.Items.UpsertTemplatePlaceholderNameAndValues(downstreamRoute.TemplatePlaceholderNameAndValues); + + _httpContext.Items.UpsertDownstreamReRoute(downstreamRoute.ReRoute.DownstreamReRoute[0]); } private void GivenTheAddHeadersToDownstreamRequestReturnsOk() @@ -92,7 +94,7 @@ namespace Ocelot.UnitTests.Headers .Verify(x => x.SetHeadersOnDownstreamRequest( It.IsAny>(), It.IsAny>(), - _downstreamContext.DownstreamRequest), Times.Once); + _httpContext.Items.DownstreamRequest()), Times.Once); } } } diff --git a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs index aa00cc34..41f33a30 100644 --- a/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpHeadersTransformationMiddlewareTests.cs @@ -1,119 +1,121 @@ -namespace Ocelot.UnitTests.Headers -{ - using Microsoft.AspNetCore.Http; - using Moq; - using Ocelot.Authorisation.Middleware; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using Ocelot.DownstreamRouteFinder; - using Ocelot.Headers; - using Ocelot.Headers.Middleware; - using Ocelot.Logging; - using Ocelot.Middleware; - using Ocelot.Request.Middleware; - using System.Collections.Generic; - using System.Net.Http; - using System.Threading.Tasks; - using TestStack.BDDfy; - using Xunit; - - public class HttpHeadersTransformationMiddlewareTests - { - private readonly Mock _preReplacer; - private readonly Mock _postReplacer; - private Mock _loggerFactory; - private Mock _logger; - private readonly HttpHeadersTransformationMiddleware _middleware; - private readonly DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; - private readonly Mock _addHeadersToResponse; - private readonly Mock _addHeadersToRequest; - - public HttpHeadersTransformationMiddlewareTests() - { - _preReplacer = new Mock(); - _postReplacer = new Mock(); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - _loggerFactory = new Mock(); - _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = context => Task.CompletedTask; - _addHeadersToResponse = new Mock(); - _addHeadersToRequest = new Mock(); - _middleware = new HttpHeadersTransformationMiddleware( - _next, _loggerFactory.Object, _preReplacer.Object, - _postReplacer.Object, _addHeadersToResponse.Object, _addHeadersToRequest.Object); - } - - [Fact] - public void should_call_pre_and_post_header_transforms() - { - this.Given(x => GivenTheFollowingRequest()) - .And(x => GivenTheDownstreamRequestIs()) - .And(x => GivenTheReRouteHasPreFindAndReplaceSetUp()) - .And(x => GivenTheHttpResponseMessageIs()) - .When(x => WhenICallTheMiddleware()) - .Then(x => ThenTheIHttpContextRequestHeaderReplacerIsCalledCorrectly()) - .Then(x => ThenAddHeadersToRequestIsCalledCorrectly()) - .And(x => ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly()) - .And(x => ThenAddHeadersToResponseIsCalledCorrectly()) - .BDDfy(); - } - - private void ThenAddHeadersToResponseIsCalledCorrectly() - { - _addHeadersToResponse - .Verify(x => x.Add(_downstreamContext.DownstreamReRoute.AddHeadersToDownstream, _downstreamContext.DownstreamResponse), Times.Once); - } - - private void ThenAddHeadersToRequestIsCalledCorrectly() - { - _addHeadersToRequest - .Verify(x => x.SetHeadersOnDownstreamRequest(_downstreamContext.DownstreamReRoute.AddHeadersToUpstream, _downstreamContext.HttpContext), Times.Once); - } - - private void WhenICallTheMiddleware() - { - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - } - - private void GivenTheDownstreamRequestIs() - { - _downstreamContext.DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); - } - - private void GivenTheHttpResponseMessageIs() - { - _downstreamContext.DownstreamResponse = new DownstreamResponse(new HttpResponseMessage()); - } - - private void GivenTheReRouteHasPreFindAndReplaceSetUp() - { - var fAndRs = new List(); - var reRoute = new ReRouteBuilder() - .WithDownstreamReRoute(new DownstreamReRouteBuilder().WithUpstreamHeaderFindAndReplace(fAndRs) - .WithDownstreamHeaderFindAndReplace(fAndRs).Build()) - .Build(); - - var dR = new DownstreamRoute(null, reRoute); - - _downstreamContext.TemplatePlaceholderNameAndValues = dR.TemplatePlaceholderNameAndValues; - _downstreamContext.DownstreamReRoute = dR.ReRoute.DownstreamReRoute[0]; - } - - private void ThenTheIHttpContextRequestHeaderReplacerIsCalledCorrectly() - { - _preReplacer.Verify(x => x.Replace(It.IsAny(), It.IsAny>()), Times.Once); - } - - private void ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly() - { - _postReplacer.Verify(x => x.Replace(It.IsAny(), It.IsAny>()), Times.Once); - } - - private void GivenTheFollowingRequest() - { - _downstreamContext.HttpContext.Request.Headers.Add("test", "test"); - } - } +namespace Ocelot.UnitTests.Headers +{ + using Microsoft.AspNetCore.Http; + using Moq; + using Ocelot.Authorisation.Middleware; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.Headers; + using Ocelot.Headers.Middleware; + using Ocelot.Logging; + using Ocelot.Middleware; + using Ocelot.Request.Middleware; + using System.Collections.Generic; + using System.Net.Http; + using System.Threading.Tasks; + using Ocelot.Infrastructure.RequestData; + using TestStack.BDDfy; + using Xunit; + using Ocelot.DownstreamRouteFinder.Middleware; + + public class HttpHeadersTransformationMiddlewareTests + { + private readonly Mock _preReplacer; + private readonly Mock _postReplacer; + private Mock _loggerFactory; + private Mock _logger; + private readonly HttpHeadersTransformationMiddleware _middleware; + private RequestDelegate _next; + private readonly Mock _addHeadersToResponse; + private readonly Mock _addHeadersToRequest; + private HttpContext _httpContext; + + public HttpHeadersTransformationMiddlewareTests() + { + _httpContext = new DefaultHttpContext(); + _preReplacer = new Mock(); + _postReplacer = new Mock(); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = context => Task.CompletedTask; + _addHeadersToResponse = new Mock(); + _addHeadersToRequest = new Mock(); + _middleware = new HttpHeadersTransformationMiddleware( + _next, _loggerFactory.Object, _preReplacer.Object, + _postReplacer.Object, _addHeadersToResponse.Object, _addHeadersToRequest.Object); + } + + [Fact] + public void should_call_pre_and_post_header_transforms() + { + this.Given(x => GivenTheFollowingRequest()) + .And(x => GivenTheDownstreamRequestIs()) + .And(x => GivenTheReRouteHasPreFindAndReplaceSetUp()) + .And(x => GivenTheHttpResponseMessageIs()) + .When(x => WhenICallTheMiddleware()) + .Then(x => ThenTheIHttpContextRequestHeaderReplacerIsCalledCorrectly()) + .Then(x => ThenAddHeadersToRequestIsCalledCorrectly()) + .And(x => ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly()) + .And(x => ThenAddHeadersToResponseIsCalledCorrectly()) + .BDDfy(); + } + + private void ThenAddHeadersToResponseIsCalledCorrectly() + { + _addHeadersToResponse + .Verify(x => x.Add(_httpContext.Items.DownstreamReRoute().AddHeadersToDownstream, _httpContext.Items.DownstreamResponse()), Times.Once); + } + + private void ThenAddHeadersToRequestIsCalledCorrectly() + { + _addHeadersToRequest + .Verify(x => x.SetHeadersOnDownstreamRequest(_httpContext.Items.DownstreamReRoute().AddHeadersToUpstream, _httpContext), Times.Once); + } + + private void WhenICallTheMiddleware() + { + _middleware.Invoke(_httpContext).GetAwaiter().GetResult(); + } + + private void GivenTheDownstreamRequestIs() + { + _httpContext.Items.UpsertDownstreamRequest(new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com"))); + } + + private void GivenTheHttpResponseMessageIs() + { + _httpContext.Items.UpsertDownstreamResponse(new DownstreamResponse(new HttpResponseMessage())); + } + + private void GivenTheReRouteHasPreFindAndReplaceSetUp() + { + var fAndRs = new List(); + var reRoute = new ReRouteBuilder() + .WithDownstreamReRoute(new DownstreamReRouteBuilder().WithUpstreamHeaderFindAndReplace(fAndRs) + .WithDownstreamHeaderFindAndReplace(fAndRs).Build()) + .Build(); + + var dR = new DownstreamRoute(null, reRoute); + + _httpContext.Items.UpsertTemplatePlaceholderNameAndValues(dR.TemplatePlaceholderNameAndValues); + _httpContext.Items.UpsertDownstreamReRoute(dR.ReRoute.DownstreamReRoute[0]); + } + + private void ThenTheIHttpContextRequestHeaderReplacerIsCalledCorrectly() + { + _preReplacer.Verify(x => x.Replace(It.IsAny(), It.IsAny>()), Times.Once); + } + + private void ThenTheIHttpResponseHeaderReplacerIsCalledCorrectly() + { + _postReplacer.Verify(x => x.Replace(It.IsAny(), It.IsAny>()), Times.Once); + } + + private void GivenTheFollowingRequest() + { + _httpContext.Request.Headers.Add("test", "test"); + } + } } diff --git a/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs b/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs index f21e9c23..99ad1458 100644 --- a/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs +++ b/test/Ocelot.UnitTests/Headers/HttpResponseHeaderReplacerTests.cs @@ -3,6 +3,7 @@ namespace Ocelot.UnitTests.Headers using Microsoft.AspNetCore.Http; using Moq; using Ocelot.Configuration; + using Ocelot.DownstreamRouteFinder.Middleware; using Ocelot.Headers; using Ocelot.Infrastructure; using Ocelot.Infrastructure.RequestData; @@ -263,8 +264,11 @@ namespace Ocelot.UnitTests.Headers private void WhenICallTheReplacer() { - var context = new DownstreamContext(new DefaultHttpContext()) { DownstreamResponse = _response, DownstreamRequest = _request }; - _result = _replacer.Replace(context, _headerFindAndReplaces); + var httpContext = new DefaultHttpContext(); + httpContext.Items.UpsertDownstreamResponse(_response); + httpContext.Items.UpsertDownstreamRequest(_request); + + _result = _replacer.Replace(httpContext, _headerFindAndReplaces); } private void ThenTheHeaderShouldBe(string key, string value) diff --git a/test/Ocelot.UnitTests/Infrastructure/ScopesAuthoriserTests.cs b/test/Ocelot.UnitTests/Infrastructure/ScopesAuthoriserTests.cs index 76901f23..08aa78a1 100644 --- a/test/Ocelot.UnitTests/Infrastructure/ScopesAuthoriserTests.cs +++ b/test/Ocelot.UnitTests/Infrastructure/ScopesAuthoriserTests.cs @@ -1,123 +1,123 @@ -using Moq; -using Ocelot.Authorisation; -using Ocelot.Errors; -using Ocelot.Infrastructure.Claims.Parser; -using Ocelot.Responses; -using Shouldly; -using System.Collections.Generic; -using System.Security.Claims; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Infrastructure -{ - public class ScopesAuthoriserTests - { - private ScopesAuthoriser _authoriser; - public Mock _parser; - private ClaimsPrincipal _principal; - private List _allowedScopes; - private Response _result; - - public ScopesAuthoriserTests() - { - _parser = new Mock(); - _authoriser = new ScopesAuthoriser(_parser.Object); - } - - [Fact] - public void should_return_ok_if_no_allowed_scopes() - { - this.Given(_ => GivenTheFollowing(new ClaimsPrincipal())) - .And(_ => GivenTheFollowing(new List())) - .When(_ => WhenIAuthorise()) - .Then(_ => ThenTheFollowingIsReturned(new OkResponse(true))) - .BDDfy(); - } - - [Fact] - public void should_return_ok_if_null_allowed_scopes() - { - this.Given(_ => GivenTheFollowing(new ClaimsPrincipal())) - .And(_ => GivenTheFollowing((List)null)) - .When(_ => WhenIAuthorise()) - .Then(_ => ThenTheFollowingIsReturned(new OkResponse(true))) - .BDDfy(); - } - - [Fact] - public void should_return_error_if_claims_parser_returns_error() - { - var fakeError = new FakeError(); - this.Given(_ => GivenTheFollowing(new ClaimsPrincipal())) - .And(_ => GivenTheParserReturns(new ErrorResponse>(fakeError))) - .And(_ => GivenTheFollowing(new List() { "doesntmatter" })) - .When(_ => WhenIAuthorise()) - .Then(_ => ThenTheFollowingIsReturned(new ErrorResponse(fakeError))) - .BDDfy(); - } - - [Fact] - public void should_match_scopes_and_return_ok_result() - { - var claimsPrincipal = new ClaimsPrincipal(); - var allowedScopes = new List() { "someScope" }; - - this.Given(_ => GivenTheFollowing(claimsPrincipal)) - .And(_ => GivenTheParserReturns(new OkResponse>(allowedScopes))) - .And(_ => GivenTheFollowing(allowedScopes)) - .When(_ => WhenIAuthorise()) - .Then(_ => ThenTheFollowingIsReturned(new OkResponse(true))) - .BDDfy(); - } - - [Fact] - public void should_not_match_scopes_and_return_error_result() - { - var fakeError = new FakeError(); - var claimsPrincipal = new ClaimsPrincipal(); - var allowedScopes = new List() { "someScope" }; - var userScopes = new List() { "anotherScope" }; - - this.Given(_ => GivenTheFollowing(claimsPrincipal)) - .And(_ => GivenTheParserReturns(new OkResponse>(userScopes))) - .And(_ => GivenTheFollowing(allowedScopes)) - .When(_ => WhenIAuthorise()) - .Then(_ => ThenTheFollowingIsReturned(new ErrorResponse(fakeError))) - .BDDfy(); - } - - private void GivenTheParserReturns(Response> response) - { - _parser.Setup(x => x.GetValuesByClaimType(It.IsAny>(), It.IsAny())).Returns(response); - } - - private void GivenTheFollowing(ClaimsPrincipal principal) - { - _principal = principal; - } - - private void GivenTheFollowing(List allowedScopes) - { - _allowedScopes = allowedScopes; - } - - private void WhenIAuthorise() - { - _result = _authoriser.Authorise(_principal, _allowedScopes); - } - - private void ThenTheFollowingIsReturned(Response expected) - { - _result.Data.ShouldBe(expected.Data); - _result.IsError.ShouldBe(expected.IsError); - } +using Moq; +using Ocelot.Authorisation; +using Ocelot.Errors; +using Ocelot.Infrastructure.Claims.Parser; +using Ocelot.Responses; +using Shouldly; +using System.Collections.Generic; +using System.Security.Claims; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Infrastructure +{ + public class ScopesAuthoriserTests + { + private ScopesAuthoriser _authoriser; + public Mock _parser; + private ClaimsPrincipal _principal; + private List _allowedScopes; + private Response _result; + + public ScopesAuthoriserTests() + { + _parser = new Mock(); + _authoriser = new ScopesAuthoriser(_parser.Object); + } + + [Fact] + public void should_return_ok_if_no_allowed_scopes() + { + this.Given(_ => GivenTheFollowing(new ClaimsPrincipal())) + .And(_ => GivenTheFollowing(new List())) + .When(_ => WhenIAuthorise()) + .Then(_ => ThenTheFollowingIsReturned(new OkResponse(true))) + .BDDfy(); + } + + [Fact] + public void should_return_ok_if_null_allowed_scopes() + { + this.Given(_ => GivenTheFollowing(new ClaimsPrincipal())) + .And(_ => GivenTheFollowing((List)null)) + .When(_ => WhenIAuthorise()) + .Then(_ => ThenTheFollowingIsReturned(new OkResponse(true))) + .BDDfy(); + } + + [Fact] + public void should_return_error_if_claims_parser_returns_error() + { + var fakeError = new FakeError(); + this.Given(_ => GivenTheFollowing(new ClaimsPrincipal())) + .And(_ => GivenTheParserReturns(new ErrorResponse>(fakeError))) + .And(_ => GivenTheFollowing(new List() { "doesntmatter" })) + .When(_ => WhenIAuthorise()) + .Then(_ => ThenTheFollowingIsReturned(new ErrorResponse(fakeError))) + .BDDfy(); + } + + [Fact] + public void should_match_scopes_and_return_ok_result() + { + var claimsPrincipal = new ClaimsPrincipal(); + var allowedScopes = new List() { "someScope" }; + + this.Given(_ => GivenTheFollowing(claimsPrincipal)) + .And(_ => GivenTheParserReturns(new OkResponse>(allowedScopes))) + .And(_ => GivenTheFollowing(allowedScopes)) + .When(_ => WhenIAuthorise()) + .Then(_ => ThenTheFollowingIsReturned(new OkResponse(true))) + .BDDfy(); + } + + [Fact] + public void should_not_match_scopes_and_return_error_result() + { + var fakeError = new FakeError(); + var claimsPrincipal = new ClaimsPrincipal(); + var allowedScopes = new List() { "someScope" }; + var userScopes = new List() { "anotherScope" }; + + this.Given(_ => GivenTheFollowing(claimsPrincipal)) + .And(_ => GivenTheParserReturns(new OkResponse>(userScopes))) + .And(_ => GivenTheFollowing(allowedScopes)) + .When(_ => WhenIAuthorise()) + .Then(_ => ThenTheFollowingIsReturned(new ErrorResponse(fakeError))) + .BDDfy(); + } + + private void GivenTheParserReturns(Response> response) + { + _parser.Setup(x => x.GetValuesByClaimType(It.IsAny>(), It.IsAny())).Returns(response); + } + + private void GivenTheFollowing(ClaimsPrincipal principal) + { + _principal = principal; + } + + private void GivenTheFollowing(List allowedScopes) + { + _allowedScopes = allowedScopes; + } + + private void WhenIAuthorise() + { + _result = _authoriser.Authorise(_principal, _allowedScopes); + } + + private void ThenTheFollowingIsReturned(Response expected) + { + _result.Data.ShouldBe(expected.Data); + _result.IsError.ShouldBe(expected.IsError); + } } public class FakeError : Error { - public FakeError() : base("fake error", OcelotErrorCode.CannotAddDataError) + public FakeError() : base("fake error", OcelotErrorCode.CannotAddDataError, 404) { } - } + } } diff --git a/test/Ocelot.UnitTests/LoadBalancer/CookieStickySessionsTests.cs b/test/Ocelot.UnitTests/LoadBalancer/CookieStickySessionsTests.cs index 401c2db1..aff599b7 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/CookieStickySessionsTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/CookieStickySessionsTests.cs @@ -21,19 +21,19 @@ namespace Ocelot.UnitTests.LoadBalancer private readonly CookieStickySessions _stickySessions; private readonly Mock _loadBalancer; private readonly int _defaultExpiryInMs; - private DownstreamContext _downstreamContext; private Response _result; private Response _firstHostAndPort; private Response _secondHostAndPort; private readonly FakeBus _bus; + private HttpContext _httpContext; public CookieStickySessionsTests() { + _httpContext = new DefaultHttpContext(); _bus = new FakeBus(); _loadBalancer = new Mock(); _defaultExpiryInMs = 0; _stickySessions = new CookieStickySessions(_loadBalancer.Object, "sessionid", _defaultExpiryInMs, _bus); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); } [Fact] @@ -116,7 +116,7 @@ namespace Ocelot.UnitTests.LoadBalancer private void GivenTheLoadBalancerReturnsError() { _loadBalancer - .Setup(x => x.Lease(It.IsAny())) + .Setup(x => x.Lease(It.IsAny())) .ReturnsAsync(new ErrorResponse(new AnyError())); } @@ -138,14 +138,14 @@ namespace Ocelot.UnitTests.LoadBalancer var cookiesTwo = new FakeCookies(); cookiesTwo.AddCookie("sessionid", "123"); contextTwo.Request.Cookies = cookiesTwo; - _firstHostAndPort = await _stickySessions.Lease(new DownstreamContext(contextOne)); - _secondHostAndPort = await _stickySessions.Lease(new DownstreamContext(contextTwo)); + _firstHostAndPort = await _stickySessions.Lease(contextOne); + _secondHostAndPort = await _stickySessions.Lease(contextTwo); } private void GivenTheLoadBalancerReturnsSequence() { _loadBalancer - .SetupSequence(x => x.Lease(It.IsAny())) + .SetupSequence(x => x.Lease(It.IsAny())) .ReturnsAsync(new OkResponse(new ServiceHostAndPort("one", 80))) .ReturnsAsync(new OkResponse(new ServiceHostAndPort("two", 80))); } @@ -158,8 +158,8 @@ namespace Ocelot.UnitTests.LoadBalancer private async Task WhenILeaseTwiceInARow() { - _firstHostAndPort = await _stickySessions.Lease(_downstreamContext); - _secondHostAndPort = await _stickySessions.Lease(_downstreamContext); + _firstHostAndPort = await _stickySessions.Lease(_httpContext); + _secondHostAndPort = await _stickySessions.Lease(_httpContext); } private void GivenTheDownstreamRequestHasSessionId(string value) @@ -168,19 +168,19 @@ namespace Ocelot.UnitTests.LoadBalancer var cookies = new FakeCookies(); cookies.AddCookie("sessionid", value); context.Request.Cookies = cookies; - _downstreamContext = new DownstreamContext(context); + _httpContext = context; } private void GivenTheLoadBalancerReturns() { _loadBalancer - .Setup(x => x.Lease(It.IsAny())) + .Setup(x => x.Lease(It.IsAny())) .ReturnsAsync(new OkResponse(new ServiceHostAndPort("", 80))); } private async Task WhenILease() { - _result = await _stickySessions.Lease(_downstreamContext); + _result = await _stickySessions.Lease(_httpContext); } private void ThenTheHostAndPortIsNotNull() diff --git a/test/Ocelot.UnitTests/LoadBalancer/DelegateInvokingLoadBalancerCreatorTests.cs b/test/Ocelot.UnitTests/LoadBalancer/DelegateInvokingLoadBalancerCreatorTests.cs index d46be02b..919c37fd 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/DelegateInvokingLoadBalancerCreatorTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/DelegateInvokingLoadBalancerCreatorTests.cs @@ -14,6 +14,8 @@ using Xunit; namespace Ocelot.UnitTests.LoadBalancer { + using Microsoft.AspNetCore.Http; + public class DelegateInvokingLoadBalancerCreatorTests { private DelegateInvokingLoadBalancerCreator _creator; @@ -113,7 +115,7 @@ namespace Ocelot.UnitTests.LoadBalancer public DownstreamReRoute ReRoute { get; } public IServiceDiscoveryProvider ServiceDiscoveryProvider { get; } - public Task> Lease(DownstreamContext context) + public Task> Lease(HttpContext httpContext) { throw new NotImplementedException(); } diff --git a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs index 1f610dd1..1979b2a0 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LeastConnectionTests.cs @@ -19,11 +19,11 @@ namespace Ocelot.UnitTests.LoadBalancer private LeastConnection _leastConnection; private List _services; private Random _random; - private DownstreamContext _context; + private HttpContext _httpContext; public LeastConnectionTests() { - _context = new DownstreamContext(new DefaultHttpContext()); + _httpContext = new DefaultHttpContext(); _random = new Random(); } @@ -64,9 +64,9 @@ namespace Ocelot.UnitTests.LoadBalancer _leastConnection = new LeastConnection(() => Task.FromResult(availableServices), serviceName); - var hostAndPortOne = _leastConnection.Lease(_context).Result; + var hostAndPortOne = _leastConnection.Lease(_httpContext).Result; hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1"); - var hostAndPortTwo = _leastConnection.Lease(_context).Result; + var hostAndPortTwo = _leastConnection.Lease(_httpContext).Result; hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.2"); _leastConnection.Release(hostAndPortOne.Data); _leastConnection.Release(hostAndPortTwo.Data); @@ -76,9 +76,9 @@ namespace Ocelot.UnitTests.LoadBalancer new Service(serviceName, new ServiceHostAndPort("127.0.0.1", 80), string.Empty, string.Empty, new string[0]), }; - hostAndPortOne = _leastConnection.Lease(_context).Result; + hostAndPortOne = _leastConnection.Lease(_httpContext).Result; hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1"); - hostAndPortTwo = _leastConnection.Lease(_context).Result; + hostAndPortTwo = _leastConnection.Lease(_httpContext).Result; hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.1"); _leastConnection.Release(hostAndPortOne.Data); _leastConnection.Release(hostAndPortTwo.Data); @@ -89,9 +89,9 @@ namespace Ocelot.UnitTests.LoadBalancer new Service(serviceName, new ServiceHostAndPort("127.0.0.2", 80), string.Empty, string.Empty, new string[0]), }; - hostAndPortOne = _leastConnection.Lease(_context).Result; + hostAndPortOne = _leastConnection.Lease(_httpContext).Result; hostAndPortOne.Data.DownstreamHost.ShouldBe("127.0.0.1"); - hostAndPortTwo = _leastConnection.Lease(_context).Result; + hostAndPortTwo = _leastConnection.Lease( _httpContext).Result; hostAndPortTwo.Data.DownstreamHost.ShouldBe("127.0.0.2"); _leastConnection.Release(hostAndPortOne.Data); _leastConnection.Release(hostAndPortTwo.Data); @@ -99,7 +99,7 @@ namespace Ocelot.UnitTests.LoadBalancer private async Task LeaseDelayAndRelease() { - var hostAndPort = await _leastConnection.Lease(_context); + var hostAndPort = await _leastConnection.Lease(_httpContext); await Task.Delay(_random.Next(1, 100)); _leastConnection.Release(hostAndPort.Data); } @@ -138,15 +138,15 @@ namespace Ocelot.UnitTests.LoadBalancer _services = availableServices; _leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName); - var response = _leastConnection.Lease(_context).Result; + var response = _leastConnection.Lease(_httpContext).Result; response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(_context).Result; + response = _leastConnection.Lease(_httpContext).Result; response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(_context).Result; + response = _leastConnection.Lease(_httpContext).Result; response.Data.DownstreamHost.ShouldBe(availableServices[2].HostAndPort.DownstreamHost); } @@ -165,19 +165,19 @@ namespace Ocelot.UnitTests.LoadBalancer _services = availableServices; _leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName); - var response = _leastConnection.Lease(_context).Result; + var response = _leastConnection.Lease(_httpContext).Result; response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(_context).Result; + response = _leastConnection.Lease(_httpContext).Result; response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(_context).Result; + response = _leastConnection.Lease(_httpContext).Result; response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(_context).Result; + response = _leastConnection.Lease(_httpContext).Result; response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); } @@ -196,26 +196,26 @@ namespace Ocelot.UnitTests.LoadBalancer _services = availableServices; _leastConnection = new LeastConnection(() => Task.FromResult(_services), serviceName); - var response = _leastConnection.Lease(_context).Result; + var response = _leastConnection.Lease(_httpContext).Result; response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(_context).Result; + response = _leastConnection.Lease(_httpContext).Result; response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(_context).Result; + response = _leastConnection.Lease(_httpContext).Result; response.Data.DownstreamHost.ShouldBe(availableServices[0].HostAndPort.DownstreamHost); - response = _leastConnection.Lease(_context).Result; + response = _leastConnection.Lease(_httpContext).Result; response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); //release this so 2 should have 1 connection and we should get 2 back as our next host and port _leastConnection.Release(availableServices[1].HostAndPort); - response = _leastConnection.Lease(_context).Result; + response = _leastConnection.Lease(_httpContext).Result; response.Data.DownstreamHost.ShouldBe(availableServices[1].HostAndPort.DownstreamHost); } @@ -276,7 +276,7 @@ namespace Ocelot.UnitTests.LoadBalancer private void WhenIGetTheNextHostAndPort() { - _result = _leastConnection.Lease(_context).Result; + _result = _leastConnection.Lease(_httpContext).Result; } private void ThenTheNextHostAndPortIsReturned() diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs index b70f8371..da8b9f09 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerFactoryTests.cs @@ -17,6 +17,7 @@ using Xunit; namespace Ocelot.UnitTests.LoadBalancer { using System; + using Microsoft.AspNetCore.Http; public class LoadBalancerFactoryTests { @@ -228,7 +229,7 @@ namespace Ocelot.UnitTests.LoadBalancer private class FakeLoadBalancerOne : ILoadBalancer { - public Task> Lease(DownstreamContext context) + public Task> Lease(HttpContext httpContext) { throw new System.NotImplementedException(); } @@ -241,7 +242,7 @@ namespace Ocelot.UnitTests.LoadBalancer private class FakeLoadBalancerTwo : ILoadBalancer { - public Task> Lease(DownstreamContext context) + public Task> Lease(HttpContext httpContext) { throw new System.NotImplementedException(); } @@ -254,7 +255,7 @@ namespace Ocelot.UnitTests.LoadBalancer private class FakeNoLoadBalancer : ILoadBalancer { - public Task> Lease(DownstreamContext context) + public Task> Lease(HttpContext httpContext) { throw new System.NotImplementedException(); } @@ -267,7 +268,7 @@ namespace Ocelot.UnitTests.LoadBalancer private class BrokenLoadBalancer : ILoadBalancer { - public Task> Lease(DownstreamContext context) + public Task> Lease(HttpContext httpContext) { throw new System.NotImplementedException(); } diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs index be43b74e..6828aa81 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerHouseTests.cs @@ -13,6 +13,8 @@ using Xunit; namespace Ocelot.UnitTests.LoadBalancer { + using Microsoft.AspNetCore.Http; + public class LoadBalancerHouseTests { private DownstreamReRoute _reRoute; @@ -155,7 +157,7 @@ namespace Ocelot.UnitTests.LoadBalancer private class FakeLoadBalancer : ILoadBalancer { - public Task> Lease(DownstreamContext context) + public Task> Lease(HttpContext httpContext) { throw new NotImplementedException(); } @@ -168,7 +170,7 @@ namespace Ocelot.UnitTests.LoadBalancer private class FakeRoundRobinLoadBalancer : ILoadBalancer { - public Task> Lease(DownstreamContext context) + public Task> Lease(HttpContext httpContext) { throw new NotImplementedException(); } diff --git a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs index 688d43f0..b06c09dd 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/LoadBalancerMiddlewareTests.cs @@ -1,7 +1,3 @@ -using System; -using System.Linq.Expressions; -using Ocelot.Middleware; - namespace Ocelot.UnitTests.LoadBalancer { using Microsoft.AspNetCore.Http; @@ -19,8 +15,13 @@ namespace Ocelot.UnitTests.LoadBalancer using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; + using Ocelot.Infrastructure.RequestData; using TestStack.BDDfy; using Xunit; + using System; + using System.Linq.Expressions; + using Ocelot.Middleware; + using Ocelot.DownstreamRouteFinder.Middleware; public class LoadBalancerMiddlewareTests { @@ -34,21 +35,22 @@ namespace Ocelot.UnitTests.LoadBalancer private Mock _loggerFactory; private Mock _logger; private LoadBalancingMiddleware _middleware; - private DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; + private RequestDelegate _next; + private HttpContext _httpContext; + private Mock _repo; public LoadBalancerMiddlewareTests() { + _repo = new Mock(); + _httpContext = new DefaultHttpContext(); _loadBalancerHouse = new Mock(); _loadBalancer = new Mock(); _loadBalancerHouse = new Mock(); _downstreamRequest = new HttpRequestMessage(HttpMethod.Get, "http://test.com/"); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); _loggerFactory = new Mock(); _logger = new Mock(); _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); _next = context => Task.CompletedTask; - _downstreamContext.DownstreamRequest = new DownstreamRequest(_downstreamRequest); } [Fact] @@ -133,34 +135,34 @@ namespace Ocelot.UnitTests.LoadBalancer private void WhenICallTheMiddleware() { _middleware = new LoadBalancingMiddleware(_next, _loggerFactory.Object, _loadBalancerHouse.Object); - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + _middleware.Invoke(_httpContext).GetAwaiter().GetResult(); } private void GivenTheConfigurationIs(ServiceProviderConfiguration config) { _config = config; var configuration = new InternalConfiguration(null, null, config, null, null, null, null, null, null); - _downstreamContext.Configuration = configuration; + _httpContext.Items.SetIInternalConfiguration(configuration); } private void GivenTheDownStreamUrlIs(string downstreamUrl) { - _downstreamRequest.RequestUri = new System.Uri(downstreamUrl); - _downstreamContext.DownstreamRequest = new DownstreamRequest(_downstreamRequest); + _downstreamRequest.RequestUri = new Uri(downstreamUrl); + _httpContext.Items.UpsertDownstreamRequest(new DownstreamRequest(_downstreamRequest)); } private void GivenTheLoadBalancerReturnsAnError() { _getHostAndPortError = new ErrorResponse(new List() { new ServicesAreNullError($"services were null for bah") }); _loadBalancer - .Setup(x => x.Lease(It.IsAny())) + .Setup(x => x.Lease(It.IsAny())) .ReturnsAsync(_getHostAndPortError); } private void GivenTheLoadBalancerReturnsOk() { _loadBalancer - .Setup(x => x.Lease(It.IsAny())) + .Setup(x => x.Lease(It.IsAny())) .ReturnsAsync(new OkResponse(new ServiceHostAndPort("abc", 123, "https"))); } @@ -168,14 +170,14 @@ namespace Ocelot.UnitTests.LoadBalancer { _hostAndPort = new ServiceHostAndPort("127.0.0.1", 80); _loadBalancer - .Setup(x => x.Lease(It.IsAny())) + .Setup(x => x.Lease(It.IsAny())) .ReturnsAsync(new OkResponse(_hostAndPort)); } private void GivenTheDownStreamRouteIs(DownstreamReRoute downstreamRoute, List placeholder) { - _downstreamContext.TemplatePlaceholderNameAndValues = placeholder; - _downstreamContext.DownstreamReRoute = downstreamRoute; + _httpContext.Items.UpsertTemplatePlaceholderNameAndValues(placeholder); + _httpContext.Items.UpsertDownstreamReRoute(downstreamRoute); } private void GivenTheLoadBalancerHouseReturns() @@ -199,32 +201,32 @@ namespace Ocelot.UnitTests.LoadBalancer private void ThenAnErrorStatingLoadBalancerCouldNotBeFoundIsSetOnPipeline() { - _downstreamContext.IsError.ShouldBeTrue(); - _downstreamContext.Errors.ShouldBe(_getLoadBalancerHouseError.Errors); + _httpContext.Items.Errors().Count.ShouldBeGreaterThan(0); + _httpContext.Items.Errors().ShouldBe(_getLoadBalancerHouseError.Errors); } private void ThenAnErrorSayingReleaseFailedIsSetOnThePipeline() { - _downstreamContext.IsError.ShouldBeTrue(); - _downstreamContext.Errors.ShouldBe(It.IsAny>()); + _httpContext.Items.Errors().Count.ShouldBeGreaterThan(0); + _httpContext.Items.Errors().ShouldBe(It.IsAny>()); } private void ThenAnErrorStatingHostAndPortCouldNotBeFoundIsSetOnPipeline() { - _downstreamContext.IsError.ShouldBeTrue(); - _downstreamContext.Errors.ShouldBe(_getHostAndPortError.Errors); + _httpContext.Items.Errors().Count.ShouldBeGreaterThan(0); + _httpContext.Items.Errors().ShouldBe(_getHostAndPortError.Errors); } private void ThenAnHostAndPortIsSetOnPipeline() { - _downstreamContext.DownstreamRequest.Host.ShouldBeEquivalentTo("abc"); - _downstreamContext.DownstreamRequest.Port.ShouldBeEquivalentTo(123); - _downstreamContext.DownstreamRequest.Scheme.ShouldBeEquivalentTo("https"); + _httpContext.Items.DownstreamRequest().Host.ShouldBeEquivalentTo("abc"); + _httpContext.Items.DownstreamRequest().Port.ShouldBeEquivalentTo(123); + _httpContext.Items.DownstreamRequest().Scheme.ShouldBeEquivalentTo("https"); } private void ThenTheDownstreamUrlIsReplacedWith(string expectedUri) { - _downstreamContext.DownstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString.ShouldBe(expectedUri); + _httpContext.Items.DownstreamRequest().ToHttpRequestMessage().RequestUri.OriginalString.ShouldBe(expectedUri); } } } diff --git a/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs b/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs index 5f10c3bc..e7601362 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/NoLoadBalancerTests.cs @@ -91,7 +91,7 @@ namespace Ocelot.UnitTests.LoadBalancer private void WhenIGetTheNextHostAndPort() { - _result = _loadBalancer.Lease(new DownstreamContext(new DefaultHttpContext())).Result; + _result = _loadBalancer.Lease(new DefaultHttpContext()).Result; } private void ThenTheHostAndPortIs(ServiceHostAndPort expected) diff --git a/test/Ocelot.UnitTests/LoadBalancer/RoundRobinTests.cs b/test/Ocelot.UnitTests/LoadBalancer/RoundRobinTests.cs index ed5fc3bc..1a1ed214 100644 --- a/test/Ocelot.UnitTests/LoadBalancer/RoundRobinTests.cs +++ b/test/Ocelot.UnitTests/LoadBalancer/RoundRobinTests.cs @@ -17,12 +17,11 @@ namespace Ocelot.UnitTests.LoadBalancer private readonly RoundRobin _roundRobin; private readonly List _services; private Response _hostAndPort; - private DownstreamContext _context; + private HttpContext _httpContext; public RoundRobinTests() { - _context = new DownstreamContext(new DefaultHttpContext()); - + _httpContext = new DefaultHttpContext(); _services = new List { new Service("product", new ServiceHostAndPort("127.0.0.1", 5000), string.Empty, string.Empty, new string[0]), @@ -52,18 +51,18 @@ namespace Ocelot.UnitTests.LoadBalancer while (stopWatch.ElapsedMilliseconds < 1000) { - var address = _roundRobin.Lease(_context).Result; + var address = _roundRobin.Lease(_httpContext).Result; address.Data.ShouldBe(_services[0].HostAndPort); - address = _roundRobin.Lease(_context).Result; + address = _roundRobin.Lease(_httpContext).Result; address.Data.ShouldBe(_services[1].HostAndPort); - address = _roundRobin.Lease(_context).Result; + address = _roundRobin.Lease(_httpContext).Result; address.Data.ShouldBe(_services[2].HostAndPort); } } private void GivenIGetTheNextAddress() { - _hostAndPort = _roundRobin.Lease(_context).Result; + _hostAndPort = _roundRobin.Lease(_httpContext).Result; } private void ThenTheNextAddressIndexIs(int index) diff --git a/test/Ocelot.UnitTests/Logging/OcelotDiagnosticListenerTests.cs b/test/Ocelot.UnitTests/Logging/OcelotDiagnosticListenerTests.cs index 41507e11..28d66f67 100644 --- a/test/Ocelot.UnitTests/Logging/OcelotDiagnosticListenerTests.cs +++ b/test/Ocelot.UnitTests/Logging/OcelotDiagnosticListenerTests.cs @@ -2,7 +2,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Moq; using Ocelot.Logging; -using Ocelot.Middleware; using System; using TestStack.BDDfy; using Xunit; @@ -16,12 +15,13 @@ namespace Ocelot.UnitTests.Logging private readonly Mock _logger; private IServiceCollection _serviceCollection; private IServiceProvider _serviceProvider; - private DownstreamContext _downstreamContext; private string _name; private Exception _exception; + private HttpContext _httpContext; public OcelotDiagnosticListenerTests() { + _httpContext = new DefaultHttpContext(); _factory = new Mock(); _logger = new Mock(); _serviceCollection = new ServiceCollection(); @@ -30,44 +30,12 @@ namespace Ocelot.UnitTests.Logging _listener = new OcelotDiagnosticListener(_factory.Object, _serviceProvider); } - [Fact] - public void should_trace_ocelot_middleware_started() - { - this.Given(_ => GivenAMiddlewareName()) - .And(_ => GivenAContext()) - .When(_ => WhenOcelotMiddlewareStartedCalled()) - .Then(_ => ThenTheLogIs($"Ocelot.MiddlewareStarted: {_name}; {_downstreamContext.HttpContext.Request.Path}")) - .BDDfy(); - } - - [Fact] - public void should_trace_ocelot_middleware_finished() - { - this.Given(_ => GivenAMiddlewareName()) - .And(_ => GivenAContext()) - .When(_ => WhenOcelotMiddlewareFinishedCalled()) - .Then(_ => ThenTheLogIs($"Ocelot.MiddlewareFinished: {_name}; {_downstreamContext.HttpContext.Request.Path}")) - .BDDfy(); - } - - [Fact] - public void should_trace_ocelot_middleware_exception() - { - this.Given(_ => GivenAMiddlewareName()) - .And(_ => GivenAContext()) - .And(_ => GivenAException(new Exception("oh no"))) - .When(_ => WhenOcelotMiddlewareExceptionCalled()) - .Then(_ => ThenTheLogIs($"Ocelot.MiddlewareException: {_name}; {_exception.Message};")) - .BDDfy(); - } - [Fact] public void should_trace_middleware_started() { this.Given(_ => GivenAMiddlewareName()) - .And(_ => GivenAContext()) .When(_ => WhenMiddlewareStartedCalled()) - .Then(_ => ThenTheLogIs($"MiddlewareStarting: {_name}; {_downstreamContext.HttpContext.Request.Path}")) + .Then(_ => ThenTheLogIs($"MiddlewareStarting: {_name}; {_httpContext.Request.Path}")) .BDDfy(); } @@ -75,9 +43,8 @@ namespace Ocelot.UnitTests.Logging public void should_trace_middleware_finished() { this.Given(_ => GivenAMiddlewareName()) - .And(_ => GivenAContext()) .When(_ => WhenMiddlewareFinishedCalled()) - .Then(_ => ThenTheLogIs($"MiddlewareFinished: {_name}; {_downstreamContext.HttpContext.Response.StatusCode}")) + .Then(_ => ThenTheLogIs($"MiddlewareFinished: {_name}; {_httpContext.Response.StatusCode}")) .BDDfy(); } @@ -85,7 +52,6 @@ namespace Ocelot.UnitTests.Logging public void should_trace_middleware_exception() { this.Given(_ => GivenAMiddlewareName()) - .And(_ => GivenAContext()) .And(_ => GivenAException(new Exception("oh no"))) .When(_ => WhenMiddlewareExceptionCalled()) .Then(_ => ThenTheLogIs($"MiddlewareException: {_name}; {_exception.Message};")) @@ -97,29 +63,14 @@ namespace Ocelot.UnitTests.Logging _exception = exception; } - private void WhenOcelotMiddlewareStartedCalled() - { - _listener.OcelotMiddlewareStarted(_downstreamContext, _name); - } - - private void WhenOcelotMiddlewareFinishedCalled() - { - _listener.OcelotMiddlewareFinished(_downstreamContext, _name); - } - - private void WhenOcelotMiddlewareExceptionCalled() - { - _listener.OcelotMiddlewareException(_exception, _downstreamContext, _name); - } - private void WhenMiddlewareStartedCalled() { - _listener.OnMiddlewareStarting(_downstreamContext.HttpContext, _name); + _listener.OnMiddlewareStarting(_httpContext, _name); } private void WhenMiddlewareFinishedCalled() { - _listener.OnMiddlewareFinished(_downstreamContext.HttpContext, _name); + _listener.OnMiddlewareFinished(_httpContext, _name); } private void WhenMiddlewareExceptionCalled() @@ -127,11 +78,6 @@ namespace Ocelot.UnitTests.Logging _listener.OnMiddlewareException(_exception, _name); } - private void GivenAContext() - { - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - } - private void GivenAMiddlewareName() { _name = "name"; diff --git a/test/Ocelot.UnitTests/Middleware/MultiplexerTests.cs b/test/Ocelot.UnitTests/Middleware/MultiplexerTests.cs deleted file mode 100644 index 7615dbfb..00000000 --- a/test/Ocelot.UnitTests/Middleware/MultiplexerTests.cs +++ /dev/null @@ -1,71 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Middleware; -using Ocelot.Middleware.Multiplexer; -using Shouldly; -using System.Threading.Tasks; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Middleware -{ - public class MultiplexerTests - { - private readonly Multiplexer _multiplexer; - private readonly DownstreamContext _context; - private ReRoute _reRoute; - private readonly OcelotRequestDelegate _pipeline; - private int _count; - private Mock _aggregator; - private Mock _factory; - - public MultiplexerTests() - { - _factory = new Mock(); - _aggregator = new Mock(); - _context = new DownstreamContext(new DefaultHttpContext()); - _pipeline = context => Task.FromResult(_count++); - _factory.Setup(x => x.Get(It.IsAny())).Returns(_aggregator.Object); - _multiplexer = new Multiplexer(_factory.Object); - } - - [Fact] - public void should_multiplex() - { - var reRoute = new ReRouteBuilder().WithDownstreamReRoute(new DownstreamReRouteBuilder().Build()).WithDownstreamReRoute(new DownstreamReRouteBuilder().Build()).Build(); - - this.Given(x => GivenTheFollowing(reRoute)) - .When(x => WhenIMultiplex()) - .Then(x => ThePipelineIsCalled(2)) - .BDDfy(); - } - - [Fact] - public void should_not_multiplex() - { - var reRoute = new ReRouteBuilder().WithDownstreamReRoute(new DownstreamReRouteBuilder().Build()).Build(); - - this.Given(x => GivenTheFollowing(reRoute)) - .When(x => WhenIMultiplex()) - .Then(x => ThePipelineIsCalled(1)) - .BDDfy(); - } - - private void GivenTheFollowing(ReRoute reRoute) - { - _reRoute = reRoute; - } - - private void WhenIMultiplex() - { - _multiplexer.Multiplex(_context, _reRoute, _pipeline).GetAwaiter().GetResult(); - } - - private void ThePipelineIsCalled(int expected) - { - _count.ShouldBe(expected); - } - } -} diff --git a/test/Ocelot.UnitTests/Middleware/OcelotMiddlewareTests.cs b/test/Ocelot.UnitTests/Middleware/OcelotMiddlewareTests.cs deleted file mode 100644 index ca5c34ef..00000000 --- a/test/Ocelot.UnitTests/Middleware/OcelotMiddlewareTests.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Microsoft.AspNetCore.Http; -using Moq; -using Ocelot.Errors; -using Ocelot.Logging; -using Ocelot.Middleware; -using Ocelot.UnitTests.Responder; -using System.Collections.Generic; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Middleware -{ - public class OcelotMiddlewareTests - { - private Mock _logger; - private FakeMiddleware _middleware; - private List _errors; - - public OcelotMiddlewareTests() - { - _errors = new List(); - _logger = new Mock(); - _middleware = new FakeMiddleware(_logger.Object); - } - - [Fact] - public void should_log_error() - { - this.Given(x => GivenAnError(new AnyError())) - .When(x => WhenISetTheError()) - .Then(x => ThenTheErrorIsLogged(1)) - .BDDfy(); - } - - [Fact] - public void should_log_errors() - { - this.Given(x => GivenAnError(new AnyError())) - .And(x => GivenAnError(new AnyError())) - .When(x => WhenISetTheErrors()) - .Then(x => ThenTheErrorIsLogged(2)) - .BDDfy(); - } - - private void WhenISetTheErrors() - { - _middleware.SetPipelineError(new DownstreamContext(new DefaultHttpContext()), _errors); - } - - private void ThenTheErrorIsLogged(int times) - { - _logger.Verify(x => x.LogWarning("blahh"), Times.Exactly(times)); - } - - private void WhenISetTheError() - { - _middleware.SetPipelineError(new DownstreamContext(new DefaultHttpContext()), _errors[0]); - } - - private void GivenAnError(Error error) - { - _errors.Add(error); - } - } - - public class FakeMiddleware : OcelotMiddleware - { - public FakeMiddleware(IOcelotLogger logger) - : base(logger) - { - } - } -} diff --git a/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs b/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs index 2e097b08..c3ba0d7a 100644 --- a/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs +++ b/test/Ocelot.UnitTests/Middleware/OcelotPipelineExtensionsTests.cs @@ -1,5 +1,7 @@ namespace Ocelot.UnitTests.Middleware { + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Ocelot.DependencyInjection; @@ -7,7 +9,6 @@ namespace Ocelot.UnitTests.Middleware using Ocelot.DownstreamUrlCreator.Middleware; using Ocelot.LoadBalancer.Middleware; using Ocelot.Middleware; - using Ocelot.Middleware.Pipeline; using Ocelot.Request.Middleware; using Ocelot.WebSockets.Middleware; using Shouldly; @@ -16,8 +17,8 @@ namespace Ocelot.UnitTests.Middleware public class OcelotPipelineExtensionsTests { - private OcelotPipelineBuilder _builder; - private OcelotRequestDelegate _handlers; + private ApplicationBuilder _builder; + private RequestDelegate _handlers; [Fact] public void should_set_up_pipeline() @@ -50,15 +51,14 @@ namespace Ocelot.UnitTests.Middleware private void WhenIExpandBuild() { OcelotPipelineConfiguration configuration = new OcelotPipelineConfiguration(); - configuration.MapWhenOcelotPipeline.Add((app) => + //Func, Action + configuration.MapWhenOcelotPipeline.Add((httpContext) => httpContext.WebSockets.IsWebSocketRequest, app => { app.UseDownstreamRouteFinderMiddleware(); app.UseDownstreamRequestInitialiser(); app.UseLoadBalancingMiddleware(); app.UseDownstreamUrlCreatorMiddleware(); app.UseWebSocketsProxyMiddleware(); - - return context => context.HttpContext.WebSockets.IsWebSocketRequest; }); _handlers = _builder.BuildOcelotPipeline(new OcelotPipelineConfiguration()); } @@ -71,7 +71,7 @@ namespace Ocelot.UnitTests.Middleware services.AddSingleton(root); services.AddOcelot(); var provider = services.BuildServiceProvider(); - _builder = new OcelotPipelineBuilder(provider); + _builder = new ApplicationBuilder(provider); } } } diff --git a/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs b/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs index 21e8dd63..e2e686d9 100644 --- a/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs +++ b/test/Ocelot.UnitTests/Middleware/OcelotPiplineBuilderTests.cs @@ -1,7 +1,4 @@ -using System; -using System.Threading.Tasks; - -namespace Ocelot.UnitTests.Middleware +namespace Ocelot.UnitTests.Middleware { using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; @@ -11,19 +8,22 @@ namespace Ocelot.UnitTests.Middleware using Ocelot.DependencyInjection; using Ocelot.Logging; using Ocelot.Middleware; - using Ocelot.Middleware.Pipeline; using Shouldly; using System.Collections.Generic; using System.Reflection; + using Microsoft.AspNetCore.Builder; + using Ocelot.Errors.Middleware; using TestStack.BDDfy; using Xunit; + using System; + using System.Threading.Tasks; public class OcelotPiplineBuilderTests { private readonly IServiceCollection _services; private readonly IConfiguration _configRoot; - private DownstreamContext _downstreamContext; private int _counter; + private HttpContext _httpContext; public OcelotPiplineBuilderTests() { @@ -32,6 +32,7 @@ namespace Ocelot.UnitTests.Middleware _services.AddSingleton(GetHostingEnvironment()); _services.AddSingleton(_configRoot); _services.AddOcelot(); + _httpContext = new DefaultHttpContext(); } @@ -64,61 +65,58 @@ namespace Ocelot.UnitTests.Middleware private void WhenIUseAGeneric() { var provider = _services.BuildServiceProvider(); - IOcelotPipelineBuilder builder = new OcelotPipelineBuilder(provider); - builder = builder.UseMiddleware(); + IApplicationBuilder builder = new ApplicationBuilder(provider); + builder = builder.UseMiddleware(); var del = builder.Build(); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - del.Invoke(_downstreamContext); + del.Invoke(_httpContext); } private void ThenTheGenericIsInThePipeline() { - _downstreamContext.HttpContext.Response.StatusCode.ShouldBe(500); + _httpContext.Response.StatusCode.ShouldBe(500); } private void WhenIUseAFunc() { _counter = 0; var provider = _services.BuildServiceProvider(); - IOcelotPipelineBuilder builder = new OcelotPipelineBuilder(provider); + IApplicationBuilder builder = new ApplicationBuilder(provider); builder = builder.Use(async (ctx, next) => { _counter++; await next.Invoke(); }); var del = builder.Build(); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - del.Invoke(_downstreamContext); + del.Invoke(_httpContext); } private void ThenTheFuncIsInThePipeline() { _counter.ShouldBe(1); - _downstreamContext.HttpContext.Response.StatusCode.ShouldBe(404); + _httpContext.Response.StatusCode.ShouldBe(404); } [Fact] public void Middleware_Multi_Parameters_Invoke() { var provider = _services.BuildServiceProvider(); - IOcelotPipelineBuilder builder = new OcelotPipelineBuilder(provider); + IApplicationBuilder builder = new ApplicationBuilder(provider); builder = builder.UseMiddleware(); var del = builder.Build(); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - del.Invoke(_downstreamContext); + del.Invoke(_httpContext); } private class MultiParametersInvokeMiddleware : OcelotMiddleware { - private readonly OcelotRequestDelegate _next; + private readonly RequestDelegate _next; - public MultiParametersInvokeMiddleware(OcelotRequestDelegate next) + public MultiParametersInvokeMiddleware(RequestDelegate next) : base(new FakeLogger()) { _next = next; } - public Task Invoke(DownstreamContext context, IServiceProvider serviceProvider) + public Task Invoke(HttpContext context, IServiceProvider serviceProvider) { return Task.CompletedTask; } diff --git a/test/Ocelot.UnitTests/Middleware/DefinedAggregatorProviderTests.cs b/test/Ocelot.UnitTests/Multiplexing/DefinedAggregatorProviderTests.cs similarity index 92% rename from test/Ocelot.UnitTests/Middleware/DefinedAggregatorProviderTests.cs rename to test/Ocelot.UnitTests/Multiplexing/DefinedAggregatorProviderTests.cs index 1874e94b..040dff2a 100644 --- a/test/Ocelot.UnitTests/Middleware/DefinedAggregatorProviderTests.cs +++ b/test/Ocelot.UnitTests/Multiplexing/DefinedAggregatorProviderTests.cs @@ -1,86 +1,86 @@ -using Microsoft.Extensions.DependencyInjection; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Middleware.Multiplexer; -using Ocelot.Responses; -using Shouldly; -using TestStack.BDDfy; -using Xunit; -using static Ocelot.UnitTests.Middleware.UserDefinedResponseAggregatorTests; +using Microsoft.Extensions.DependencyInjection; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.Multiplexer; +using Ocelot.Responses; +using Shouldly; +using TestStack.BDDfy; +using Xunit; +using static Ocelot.UnitTests.Multiplexing.UserDefinedResponseAggregatorTests; -namespace Ocelot.UnitTests.Middleware -{ - public class DefinedAggregatorProviderTests - { - private ServiceLocatorDefinedAggregatorProvider _provider; - private Response _aggregator; - private ReRoute _reRoute; - - [Fact] - public void should_find_aggregator() - { - var reRoute = new ReRouteBuilder() - .WithAggregator("TestDefinedAggregator") - .Build(); - - this.Given(_ => GivenDefinedAggregator()) - .And(_ => GivenReRoute(reRoute)) - .When(_ => WhenIGet()) - .Then(_ => ThenTheAggregatorIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_not_find_aggregator() - { - var reRoute = new ReRouteBuilder() - .WithAggregator("TestDefinedAggregator") - .Build(); - - this.Given(_ => GivenNoDefinedAggregator()) - .And(_ => GivenReRoute(reRoute)) - .When(_ => WhenIGet()) - .Then(_ => ThenAnErrorIsReturned()) - .BDDfy(); - } - - private void GivenDefinedAggregator() - { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(); - var services = serviceCollection.BuildServiceProvider(); - _provider = new ServiceLocatorDefinedAggregatorProvider(services); - } - - private void ThenTheAggregatorIsReturned() - { - _aggregator.Data.ShouldNotBeNull(); - _aggregator.Data.ShouldBeOfType(); - _aggregator.IsError.ShouldBeFalse(); - } - - private void GivenNoDefinedAggregator() - { - var serviceCollection = new ServiceCollection(); - var services = serviceCollection.BuildServiceProvider(); - _provider = new ServiceLocatorDefinedAggregatorProvider(services); - } - - private void GivenReRoute(ReRoute reRoute) - { - _reRoute = reRoute; - } - - private void WhenIGet() - { - _aggregator = _provider.Get(_reRoute); - } - - private void ThenAnErrorIsReturned() - { - _aggregator.IsError.ShouldBeTrue(); - _aggregator.Errors[0].Message.ShouldBe("Could not find Aggregator: TestDefinedAggregator"); - _aggregator.Errors[0].ShouldBeOfType(); - } - } -} +namespace Ocelot.UnitTests.Multiplexing +{ + public class DefinedAggregatorProviderTests + { + private ServiceLocatorDefinedAggregatorProvider _provider; + private Response _aggregator; + private ReRoute _reRoute; + + [Fact] + public void should_find_aggregator() + { + var reRoute = new ReRouteBuilder() + .WithAggregator("TestDefinedAggregator") + .Build(); + + this.Given(_ => GivenDefinedAggregator()) + .And(_ => GivenReRoute(reRoute)) + .When(_ => WhenIGet()) + .Then(_ => ThenTheAggregatorIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_not_find_aggregator() + { + var reRoute = new ReRouteBuilder() + .WithAggregator("TestDefinedAggregator") + .Build(); + + this.Given(_ => GivenNoDefinedAggregator()) + .And(_ => GivenReRoute(reRoute)) + .When(_ => WhenIGet()) + .Then(_ => ThenAnErrorIsReturned()) + .BDDfy(); + } + + private void GivenDefinedAggregator() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(); + var services = serviceCollection.BuildServiceProvider(); + _provider = new ServiceLocatorDefinedAggregatorProvider(services); + } + + private void ThenTheAggregatorIsReturned() + { + _aggregator.Data.ShouldNotBeNull(); + _aggregator.Data.ShouldBeOfType(); + _aggregator.IsError.ShouldBeFalse(); + } + + private void GivenNoDefinedAggregator() + { + var serviceCollection = new ServiceCollection(); + var services = serviceCollection.BuildServiceProvider(); + _provider = new ServiceLocatorDefinedAggregatorProvider(services); + } + + private void GivenReRoute(ReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenIGet() + { + _aggregator = _provider.Get(_reRoute); + } + + private void ThenAnErrorIsReturned() + { + _aggregator.IsError.ShouldBeTrue(); + _aggregator.Errors[0].Message.ShouldBe("Could not find Aggregator: TestDefinedAggregator"); + _aggregator.Errors[0].ShouldBeOfType(); + } + } +} diff --git a/test/Ocelot.UnitTests/Multiplexing/MultiplexingMiddlewareTests.cs b/test/Ocelot.UnitTests/Multiplexing/MultiplexingMiddlewareTests.cs new file mode 100644 index 00000000..e633d244 --- /dev/null +++ b/test/Ocelot.UnitTests/Multiplexing/MultiplexingMiddlewareTests.cs @@ -0,0 +1,81 @@ +namespace Ocelot.UnitTests.Multiplexing +{ + using Microsoft.AspNetCore.Http; + using Moq; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Logging; + using Ocelot.Middleware; + using Ocelot.Multiplexer; + using Shouldly; + using System.Collections.Generic; + using System.Threading.Tasks; + using TestStack.BDDfy; + using Xunit; + + public class MultiplexingMiddlewareTests + { + private readonly MultiplexingMiddleware _middleware; + private DownstreamRoute _downstreamRoute; + private int _count; + private Mock _aggregator; + private Mock _factory; + private HttpContext _httpContext; + private RequestDelegate _next; + private Mock _loggerFactory; + private Mock _logger; + + public MultiplexingMiddlewareTests() + { + _httpContext = new DefaultHttpContext(); + _factory = new Mock(); + _aggregator = new Mock(); + _factory.Setup(x => x.Get(It.IsAny())).Returns(_aggregator.Object); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = context => Task.FromResult(_count++); + _middleware = new MultiplexingMiddleware(_next, _loggerFactory.Object, _factory.Object); + } + + [Fact] + public void should_multiplex() + { + var reRoute = new ReRouteBuilder().WithDownstreamReRoute(new DownstreamReRouteBuilder().Build()).WithDownstreamReRoute(new DownstreamReRouteBuilder().Build()).Build(); + + this.Given(x => GivenTheFollowing(reRoute)) + .When(x => WhenIMultiplex()) + .Then(x => ThePipelineIsCalled(2)) + .BDDfy(); + } + + [Fact] + public void should_not_multiplex() + { + var reRoute = new ReRouteBuilder().WithDownstreamReRoute(new DownstreamReRouteBuilder().Build()).Build(); + + this.Given(x => GivenTheFollowing(reRoute)) + .When(x => WhenIMultiplex()) + .Then(x => ThePipelineIsCalled(1)) + .BDDfy(); + } + + private void GivenTheFollowing(ReRoute reRoute) + { + _downstreamRoute = new DownstreamRoute(new List(), reRoute); + _httpContext.Items.UpsertDownstreamRoute(_downstreamRoute); + } + + private void WhenIMultiplex() + { + _middleware.Invoke(_httpContext).GetAwaiter().GetResult(); + } + + private void ThePipelineIsCalled(int expected) + { + _count.ShouldBe(expected); + } + } +} diff --git a/test/Ocelot.UnitTests/Middleware/ResponseAggregatorFactoryTests.cs b/test/Ocelot.UnitTests/Multiplexing/ResponseAggregatorFactoryTests.cs similarity index 93% rename from test/Ocelot.UnitTests/Middleware/ResponseAggregatorFactoryTests.cs rename to test/Ocelot.UnitTests/Multiplexing/ResponseAggregatorFactoryTests.cs index 73f80f6f..86bb570e 100644 --- a/test/Ocelot.UnitTests/Middleware/ResponseAggregatorFactoryTests.cs +++ b/test/Ocelot.UnitTests/Multiplexing/ResponseAggregatorFactoryTests.cs @@ -1,65 +1,65 @@ -namespace Ocelot.UnitTests.Middleware -{ - using Moq; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using Ocelot.Middleware.Multiplexer; - using Shouldly; - using TestStack.BDDfy; - using Xunit; - - public class ResponseAggregatorFactoryTests - { - private readonly InMemoryResponseAggregatorFactory _factory; - private Mock _provider; - private ReRoute _reRoute; - private IResponseAggregator _aggregator; - - public ResponseAggregatorFactoryTests() - { - _provider = new Mock(); - _aggregator = new SimpleJsonResponseAggregator(); - _factory = new InMemoryResponseAggregatorFactory(_provider.Object, _aggregator); - } - - [Fact] - public void should_return_simple_json_aggregator() - { - var reRoute = new ReRouteBuilder() - .Build(); - - this.Given(_ => GivenReRoute(reRoute)) - .When(_ => WhenIGet()) - .Then(_ => ThenTheAggregatorIs()) - .BDDfy(); - } - - [Fact] - public void should_return_user_defined_aggregator() - { - var reRoute = new ReRouteBuilder() - .WithAggregator("doesntmatter") - .Build(); - - this.Given(_ => GivenReRoute(reRoute)) - .When(_ => WhenIGet()) - .Then(_ => ThenTheAggregatorIs()) - .BDDfy(); - } - - private void GivenReRoute(ReRoute reRoute) - { - _reRoute = reRoute; - } - - private void WhenIGet() - { - _aggregator = _factory.Get(_reRoute); - } - - private void ThenTheAggregatorIs() - { - _aggregator.ShouldBeOfType(); - } - } -} +namespace Ocelot.UnitTests.Multiplexing +{ + using Moq; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.Multiplexer; + using Shouldly; + using TestStack.BDDfy; + using Xunit; + + public class ResponseAggregatorFactoryTests + { + private readonly InMemoryResponseAggregatorFactory _factory; + private Mock _provider; + private ReRoute _reRoute; + private IResponseAggregator _aggregator; + + public ResponseAggregatorFactoryTests() + { + _provider = new Mock(); + _aggregator = new SimpleJsonResponseAggregator(); + _factory = new InMemoryResponseAggregatorFactory(_provider.Object, _aggregator); + } + + [Fact] + public void should_return_simple_json_aggregator() + { + var reRoute = new ReRouteBuilder() + .Build(); + + this.Given(_ => GivenReRoute(reRoute)) + .When(_ => WhenIGet()) + .Then(_ => ThenTheAggregatorIs()) + .BDDfy(); + } + + [Fact] + public void should_return_user_defined_aggregator() + { + var reRoute = new ReRouteBuilder() + .WithAggregator("doesntmatter") + .Build(); + + this.Given(_ => GivenReRoute(reRoute)) + .When(_ => WhenIGet()) + .Then(_ => ThenTheAggregatorIs()) + .BDDfy(); + } + + private void GivenReRoute(ReRoute reRoute) + { + _reRoute = reRoute; + } + + private void WhenIGet() + { + _aggregator = _factory.Get(_reRoute); + } + + private void ThenTheAggregatorIs() + { + _aggregator.ShouldBeOfType(); + } + } +} diff --git a/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs b/test/Ocelot.UnitTests/Multiplexing/SimpleJsonResponseAggregatorTests.cs similarity index 57% rename from test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs rename to test/Ocelot.UnitTests/Multiplexing/SimpleJsonResponseAggregatorTests.cs index 8d17d792..5950d084 100644 --- a/test/Ocelot.UnitTests/Middleware/SimpleJsonResponseAggregatorTests.cs +++ b/test/Ocelot.UnitTests/Multiplexing/SimpleJsonResponseAggregatorTests.cs @@ -4,7 +4,7 @@ using Ocelot.Configuration; using Ocelot.Configuration.Builder; using Ocelot.Configuration.File; using Ocelot.Middleware; -using Ocelot.Middleware.Multiplexer; +using Ocelot.Multiplexer; using Ocelot.UnitTests.Responder; using Ocelot.Values; using Shouldly; @@ -15,13 +15,13 @@ using System.Text; using TestStack.BDDfy; using Xunit; -namespace Ocelot.UnitTests.Middleware +namespace Ocelot.UnitTests.Multiplexing { public class SimpleJsonResponseAggregatorTests { private readonly SimpleJsonResponseAggregator _aggregator; - private List _downstreamContexts; - private DownstreamContext _upstreamContext; + private List _downstreamContexts; + private HttpContext _upstreamContext; private ReRoute _reRoute; public SimpleJsonResponseAggregatorTests() @@ -53,24 +53,21 @@ namespace Ocelot.UnitTests.Middleware .Build(); var commentsResponseContent = @"[{""id"":1,""writerId"":1,""postId"":1,""text"":""text1""},{""id"":2,""writerId"":2,""postId"":2,""text"":""text2""},{""id"":3,""writerId"":2,""postId"":1,""text"":""text21""}]"; - var commentsDownstreamContext = new DownstreamContext(new DefaultHttpContext()) - { - DownstreamResponse = new DownstreamResponse(new StringContent(commentsResponseContent, Encoding.UTF8, "application/json"), HttpStatusCode.OK, new EditableList>>(), "some reason"), - DownstreamReRoute = commentsDownstreamReRoute - }; + + var commentsDownstreamContext = new DefaultHttpContext(); + commentsDownstreamContext.Items.UpsertDownstreamResponse(new DownstreamResponse(new StringContent(commentsResponseContent, Encoding.UTF8, "application/json"), HttpStatusCode.OK, new EditableList>>(), "some reason")); + commentsDownstreamContext.Items.UpsertDownstreamReRoute(commentsDownstreamReRoute); var userDetailsResponseContent = @"[{""id"":1,""firstName"":""abolfazl"",""lastName"":""rajabpour""},{""id"":2,""firstName"":""reza"",""lastName"":""rezaei""}]"; - var userDetailsDownstreamContext = new DownstreamContext(new DefaultHttpContext()) - { - DownstreamResponse = new DownstreamResponse(new StringContent(userDetailsResponseContent, Encoding.UTF8, "application/json"), HttpStatusCode.OK, new List>>(), "some reason"), - DownstreamReRoute = userDetailsDownstreamReRoute - }; + var userDetailsDownstreamContext = new DefaultHttpContext(); + userDetailsDownstreamContext.Items.UpsertDownstreamResponse(new DownstreamResponse(new StringContent(userDetailsResponseContent, Encoding.UTF8, "application/json"), HttpStatusCode.OK, new List>>(), "some reason")); + userDetailsDownstreamContext.Items.UpsertDownstreamReRoute(userDetailsDownstreamReRoute); - var downstreamContexts = new List { commentsDownstreamContext, userDetailsDownstreamContext }; + var downstreamContexts = new List { commentsDownstreamContext, userDetailsDownstreamContext }; var expected = "{\"Comments\":" + commentsResponseContent + ",\"UserDetails\":" + userDetailsResponseContent + "}"; - this.Given(x => GivenTheUpstreamContext(new DownstreamContext(new DefaultHttpContext()))) + this.Given(x => GivenTheUpstreamContext(new DefaultHttpContext())) .And(x => GivenTheReRoute(reRoute)) .And(x => GivenTheDownstreamContext(downstreamContexts)) .When(x => WhenIAggregate()) @@ -97,23 +94,19 @@ namespace Ocelot.UnitTests.Middleware .WithDownstreamReRoutes(downstreamReRoutes) .Build(); - var billDownstreamContext = new DownstreamContext(new DefaultHttpContext()) - { - DownstreamResponse = new DownstreamResponse(new StringContent("Bill says hi"), HttpStatusCode.OK, new EditableList>>(), "some reason"), - DownstreamReRoute = billDownstreamReRoute - }; + var billDownstreamContext = new DefaultHttpContext(); + billDownstreamContext.Items.UpsertDownstreamResponse(new DownstreamResponse(new StringContent("Bill says hi"), HttpStatusCode.OK, new EditableList>>(), "some reason")); + billDownstreamContext.Items.UpsertDownstreamReRoute(billDownstreamReRoute); - var georgeDownstreamContext = new DownstreamContext(new DefaultHttpContext()) - { - DownstreamResponse = new DownstreamResponse(new StringContent("George says hi"), HttpStatusCode.OK, new List>>(), "some reason"), - DownstreamReRoute = georgeDownstreamReRoute - }; + var georgeDownstreamContext = new DefaultHttpContext(); + georgeDownstreamContext.Items.UpsertDownstreamResponse(new DownstreamResponse(new StringContent("George says hi"), HttpStatusCode.OK, new List>>(), "some reason")); + georgeDownstreamContext.Items.UpsertDownstreamReRoute(georgeDownstreamReRoute); - var downstreamContexts = new List { billDownstreamContext, georgeDownstreamContext }; + var downstreamContexts = new List { billDownstreamContext, georgeDownstreamContext }; var expected = "{\"Bill\":Bill says hi,\"George\":George says hi}"; - this.Given(x => GivenTheUpstreamContext(new DownstreamContext(new DefaultHttpContext()))) + this.Given(x => GivenTheUpstreamContext(new DefaultHttpContext())) .And(x => GivenTheReRoute(reRoute)) .And(x => GivenTheDownstreamContext(downstreamContexts)) .When(x => WhenIAggregate()) @@ -140,25 +133,21 @@ namespace Ocelot.UnitTests.Middleware .WithDownstreamReRoutes(downstreamReRoutes) .Build(); - var billDownstreamContext = new DownstreamContext(new DefaultHttpContext()) - { - DownstreamResponse = new DownstreamResponse(new StringContent("Bill says hi"), HttpStatusCode.OK, new List>>(), "some reason"), - DownstreamReRoute = billDownstreamReRoute - }; + var billDownstreamContext = new DefaultHttpContext(); + billDownstreamContext.Items.UpsertDownstreamResponse(new DownstreamResponse(new StringContent("Bill says hi"), HttpStatusCode.OK, new List>>(), "some reason")); + billDownstreamContext.Items.UpsertDownstreamReRoute(billDownstreamReRoute); - var georgeDownstreamContext = new DownstreamContext(new DefaultHttpContext()) - { - DownstreamResponse = new DownstreamResponse(new StringContent("Error"), HttpStatusCode.OK, new List>>(), "some reason"), - DownstreamReRoute = georgeDownstreamReRoute, - }; + var georgeDownstreamContext = new DefaultHttpContext(); + georgeDownstreamContext.Items.UpsertDownstreamResponse(new DownstreamResponse(new StringContent("Error"), HttpStatusCode.OK, new List>>(), "some reason")); + georgeDownstreamContext.Items.UpsertDownstreamReRoute(georgeDownstreamReRoute); - georgeDownstreamContext.Errors.Add(new AnyError()); + georgeDownstreamContext.Items.SetError(new AnyError()); - var downstreamContexts = new List { billDownstreamContext, georgeDownstreamContext }; + var downstreamContexts = new List { billDownstreamContext, georgeDownstreamContext }; var expected = "Error"; - this.Given(x => GivenTheUpstreamContext(new DownstreamContext(new DefaultHttpContext()))) + this.Given(x => GivenTheUpstreamContext(new DefaultHttpContext())) .And(x => GivenTheReRoute(reRoute)) .And(x => GivenTheDownstreamContext(downstreamContexts)) .When(x => WhenIAggregate()) @@ -169,13 +158,13 @@ namespace Ocelot.UnitTests.Middleware private void ThenTheReasonPhraseIs(string expected) { - _upstreamContext.DownstreamResponse.ReasonPhrase.ShouldBe(expected); + _upstreamContext.Items.DownstreamResponse().ReasonPhrase.ShouldBe(expected); } private void ThenTheErrorIsMapped() { - _upstreamContext.Errors.ShouldBe(_downstreamContexts[1].Errors); - _upstreamContext.DownstreamResponse.ShouldBe(_downstreamContexts[1].DownstreamResponse); + _upstreamContext.Items.Errors().ShouldBe(_downstreamContexts[1].Items.Errors()); + _upstreamContext.Items.DownstreamResponse().ShouldBe(_downstreamContexts[1].Items.DownstreamResponse()); } private void GivenTheReRoute(ReRoute reRoute) @@ -183,12 +172,12 @@ namespace Ocelot.UnitTests.Middleware _reRoute = reRoute; } - private void GivenTheUpstreamContext(DownstreamContext upstreamContext) + private void GivenTheUpstreamContext(HttpContext upstreamContext) { _upstreamContext = upstreamContext; } - private void GivenTheDownstreamContext(List downstreamContexts) + private void GivenTheDownstreamContext(List downstreamContexts) { _downstreamContexts = downstreamContexts; } @@ -200,7 +189,7 @@ namespace Ocelot.UnitTests.Middleware private void ThenTheContentIs(string expected) { - var content = _upstreamContext.DownstreamResponse.Content.ReadAsStringAsync() + var content = _upstreamContext.Items.DownstreamResponse().Content.ReadAsStringAsync() .GetAwaiter() .GetResult(); @@ -209,14 +198,14 @@ namespace Ocelot.UnitTests.Middleware private void ThenTheContentTypeIs(string expected) { - _upstreamContext.DownstreamResponse.Content.Headers.ContentType.MediaType.ShouldBe(expected); + _upstreamContext.Items.DownstreamResponse().Content.Headers.ContentType.MediaType.ShouldBe(expected); } private void ThenTheUpstreamContextIsMappedForNonAggregate() { - _upstreamContext.DownstreamRequest.ShouldBe(_downstreamContexts[0].DownstreamRequest); - _upstreamContext.DownstreamResponse.ShouldBe(_downstreamContexts[0].DownstreamResponse); - _upstreamContext.Errors.ShouldBe(_downstreamContexts[0].Errors); + _upstreamContext.Items.DownstreamRequest().ShouldBe(_downstreamContexts[0].Items.DownstreamRequest()); + _upstreamContext.Items.DownstreamRequest().ShouldBe(_downstreamContexts[0].Items.DownstreamRequest()); + _upstreamContext.Items.Errors().ShouldBe(_downstreamContexts[0].Items.Errors()); } } } diff --git a/test/Ocelot.UnitTests/Middleware/UserDefinedResponseAggregatorTests.cs b/test/Ocelot.UnitTests/Multiplexing/UserDefinedResponseAggregatorTests.cs similarity index 54% rename from test/Ocelot.UnitTests/Middleware/UserDefinedResponseAggregatorTests.cs rename to test/Ocelot.UnitTests/Multiplexing/UserDefinedResponseAggregatorTests.cs index 78d53878..aa9e26ef 100644 --- a/test/Ocelot.UnitTests/Middleware/UserDefinedResponseAggregatorTests.cs +++ b/test/Ocelot.UnitTests/Multiplexing/UserDefinedResponseAggregatorTests.cs @@ -1,29 +1,29 @@ -using Microsoft.AspNetCore.Http; -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Middleware; -using Ocelot.Middleware.Multiplexer; -using Ocelot.Responses; -using Ocelot.UnitTests.Responder; -using Shouldly; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Middleware +namespace Ocelot.UnitTests.Multiplexing { + using Microsoft.AspNetCore.Http; + using Moq; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.Middleware; + using Ocelot.Multiplexer; + using Ocelot.Responses; + using Ocelot.UnitTests.Responder; + using Shouldly; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Threading.Tasks; + using TestStack.BDDfy; + using Xunit; + public class UserDefinedResponseAggregatorTests { private readonly UserDefinedResponseAggregator _aggregator; private readonly Mock _provider; private ReRoute _reRoute; - private List _contexts; - private DownstreamContext _context; + private List _contexts; + private HttpContext _context; public UserDefinedResponseAggregatorTests() { @@ -36,18 +36,18 @@ namespace Ocelot.UnitTests.Middleware { var reRoute = new ReRouteBuilder().Build(); - var context = new DownstreamContext(new DefaultHttpContext()); + var context = new DefaultHttpContext(); - var contexts = new List + var contextA = new DefaultHttpContext(); + contextA.Items.UpsertDownstreamResponse(new DownstreamResponse(new StringContent("Tom"), HttpStatusCode.OK, new List>>(), "some reason")); + + var contextB = new DefaultHttpContext(); + contextB.Items.UpsertDownstreamResponse(new DownstreamResponse(new StringContent("Laura"), HttpStatusCode.OK, new List>>(), "some reason")); + + var contexts = new List() { - new DownstreamContext(new DefaultHttpContext()) - { - DownstreamResponse = new DownstreamResponse(new StringContent("Tom"), HttpStatusCode.OK, new List>>(), "some reason") - }, - new DownstreamContext(new DefaultHttpContext()) - { - DownstreamResponse = new DownstreamResponse(new StringContent("Laura"), HttpStatusCode.OK, new List>>(), "some reason") - } + contextA, + contextB, }; this.Given(_ => GivenTheProviderReturnsAggregator()) @@ -65,18 +65,18 @@ namespace Ocelot.UnitTests.Middleware { var reRoute = new ReRouteBuilder().Build(); - var context = new DownstreamContext(new DefaultHttpContext()); + var context = new DefaultHttpContext(); - var contexts = new List + var contextA = new DefaultHttpContext(); + contextA.Items.UpsertDownstreamResponse(new DownstreamResponse(new StringContent("Tom"), HttpStatusCode.OK, new List>>(), "some reason")); + + var contextB = new DefaultHttpContext(); + contextB.Items.UpsertDownstreamResponse(new DownstreamResponse(new StringContent("Laura"), HttpStatusCode.OK, new List>>(), "some reason")); + + var contexts = new List() { - new DownstreamContext(new DefaultHttpContext()) - { - DownstreamResponse = new DownstreamResponse(new StringContent("Tom"), HttpStatusCode.OK, new List>>(), "some reason") - }, - new DownstreamContext(new DefaultHttpContext()) - { - DownstreamResponse = new DownstreamResponse(new StringContent("Laura"), HttpStatusCode.OK, new List>>(), "some reason") - } + contextA, + contextB, }; this.Given(_ => GivenTheProviderReturnsError()) @@ -91,8 +91,8 @@ namespace Ocelot.UnitTests.Middleware private void ThenTheErrorIsReturned() { - _context.IsError.ShouldBeTrue(); - _context.Errors.Count.ShouldBe(1); + _context.Items.Errors().Count.ShouldBeGreaterThan(0); + _context.Items.Errors().Count.ShouldBe(1); } private void GivenTheProviderReturnsError() @@ -102,7 +102,7 @@ namespace Ocelot.UnitTests.Middleware private async Task ThenTheContentIsCorrect() { - var content = await _context.DownstreamResponse.Content.ReadAsStringAsync(); + var content = await _context.Items.DownstreamResponse().Content.ReadAsStringAsync(); content.ShouldBe("Tom, Laura"); } @@ -111,12 +111,12 @@ namespace Ocelot.UnitTests.Middleware _provider.Verify(x => x.Get(_reRoute), Times.Once); } - private void GivenContext(DownstreamContext context) + private void GivenContext(HttpContext context) { _context = context; } - private void GivenContexts(List contexts) + private void GivenContexts(List contexts) { _contexts = contexts; } @@ -139,12 +139,12 @@ namespace Ocelot.UnitTests.Middleware public class TestDefinedAggregator : IDefinedAggregator { - public async Task Aggregate(List responses) + public async Task Aggregate(List responses) { - var tom = await responses[0].DownstreamResponse.Content.ReadAsStringAsync(); - var laura = await responses[1].DownstreamResponse.Content.ReadAsStringAsync(); + var tom = await responses[0].Items.DownstreamResponse().Content.ReadAsStringAsync(); + var laura = await responses[1].Items.DownstreamResponse().Content.ReadAsStringAsync(); var content = $"{tom}, {laura}"; - var headers = responses.SelectMany(x => x.DownstreamResponse.Headers).ToList(); + var headers = responses.SelectMany(x => x.Items.DownstreamResponse().Headers).ToList(); return new DownstreamResponse(new StringContent(content), HttpStatusCode.OK, headers, "some reason"); } } diff --git a/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs b/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs index d82fbc5b..262cda84 100644 --- a/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs +++ b/test/Ocelot.UnitTests/QueryStrings/AddQueriesToRequestTests.cs @@ -1,194 +1,194 @@ -using Moq; -using Ocelot.Configuration; -using Ocelot.Errors; -using Ocelot.Infrastructure.Claims.Parser; -using Ocelot.QueryStrings; -using Ocelot.Request.Middleware; -using Ocelot.Responses; -using Shouldly; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Security.Claims; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.QueryStrings -{ - public class AddQueriesToRequestTests - { - private readonly AddQueriesToRequest _addQueriesToRequest; - private DownstreamRequest _downstreamRequest; - private readonly Mock _parser; - private List _configuration; - private List _claims; - private Response _result; - private Response _claimValue; - private HttpRequestMessage _request; - - public AddQueriesToRequestTests() - { - _request = new HttpRequestMessage(HttpMethod.Post, "http://my.url/abc?q=123"); - _parser = new Mock(); - _addQueriesToRequest = new AddQueriesToRequest(_parser.Object); - _downstreamRequest = new DownstreamRequest(_request); - } - - [Fact] - public void should_add_new_queries_to_downstream_request() - { - var claims = new List - { - new Claim("test", "data") - }; - - this.Given( - x => x.GivenAClaimToThing(new List - { - new ClaimToThing("query-key", "", "", 0) - })) - .Given(x => x.GivenClaims(claims)) - .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) - .When(x => x.WhenIAddQueriesToTheRequest()) - .Then(x => x.ThenTheResultIsSuccess()) - .And(x => x.ThenTheQueryIsAdded()) - .BDDfy(); - } - - [Fact] - public void should_add_new_queries_to_downstream_request_and_preserve_other_queries() - { - var claims = new List - { - new Claim("test", "data") - }; - - this.Given( - x => x.GivenAClaimToThing(new List - { - new ClaimToThing("query-key", "", "", 0) - })) - .Given(x => x.GivenClaims(claims)) - .And(x => GivenTheDownstreamRequestHasQueryString("?test=1&test=2")) - .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) - .When(x => x.WhenIAddQueriesToTheRequest()) - .Then(x => x.ThenTheResultIsSuccess()) - .And(x => x.ThenTheQueryIsAdded()) - .And(x => TheTheQueryStringIs("?test=1&test=2&query-key=value")) - .BDDfy(); - } - - private void TheTheQueryStringIs(string expected) - { - _downstreamRequest.Query.ShouldBe(expected); - } - - [Fact] - public void should_replace_existing_queries_on_downstream_request() - { - var claims = new List - { - new Claim("test", "data") - }; - - this.Given( - x => x.GivenAClaimToThing(new List - { - new ClaimToThing("query-key", "", "", 0) - })) - .And(x => x.GivenClaims(claims)) - .And(x => x.GivenTheDownstreamRequestHasQueryString("query-key", "initial")) - .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) - .When(x => x.WhenIAddQueriesToTheRequest()) - .Then(x => x.ThenTheResultIsSuccess()) - .And(x => x.ThenTheQueryIsAdded()) - .BDDfy(); - } - - [Fact] - public void should_return_error() - { - this.Given( - x => x.GivenAClaimToThing(new List - { - new ClaimToThing("", "", "", 0) - })) - .Given(x => x.GivenClaims(new List())) - .And(x => x.GivenTheClaimParserReturns(new ErrorResponse(new List - { - new AnyError() - }))) - .When(x => x.WhenIAddQueriesToTheRequest()) - .Then(x => x.ThenTheResultIsError()) - .BDDfy(); - } - - private void ThenTheQueryIsAdded() - { - var queries = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(_downstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString); - var query = queries.First(x => x.Key == "query-key"); - query.Value.First().ShouldBe(_claimValue.Data); - } - - private void GivenAClaimToThing(List configuration) - { - _configuration = configuration; - } - - private void GivenClaims(List claims) - { - _claims = claims; - } - - private void GivenTheDownstreamRequestHasQueryString(string queryString) - { - _request = new HttpRequestMessage(HttpMethod.Post, $"http://my.url/abc{queryString}"); - _downstreamRequest = new DownstreamRequest(_request); - } - - private void GivenTheDownstreamRequestHasQueryString(string key, string value) - { - var newUri = Microsoft.AspNetCore.WebUtilities.QueryHelpers - .AddQueryString(_downstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString, key, value); - - _request.RequestUri = new Uri(newUri); - } - - private void GivenTheClaimParserReturns(Response claimValue) - { - _claimValue = claimValue; - _parser - .Setup( - x => +using Moq; +using Ocelot.Configuration; +using Ocelot.Errors; +using Ocelot.Infrastructure.Claims.Parser; +using Ocelot.QueryStrings; +using Ocelot.Request.Middleware; +using Ocelot.Responses; +using Shouldly; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Security.Claims; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.QueryStrings +{ + public class AddQueriesToRequestTests + { + private readonly AddQueriesToRequest _addQueriesToRequest; + private DownstreamRequest _downstreamRequest; + private readonly Mock _parser; + private List _configuration; + private List _claims; + private Response _result; + private Response _claimValue; + private HttpRequestMessage _request; + + public AddQueriesToRequestTests() + { + _request = new HttpRequestMessage(HttpMethod.Post, "http://my.url/abc?q=123"); + _parser = new Mock(); + _addQueriesToRequest = new AddQueriesToRequest(_parser.Object); + _downstreamRequest = new DownstreamRequest(_request); + } + + [Fact] + public void should_add_new_queries_to_downstream_request() + { + var claims = new List + { + new Claim("test", "data") + }; + + this.Given( + x => x.GivenAClaimToThing(new List + { + new ClaimToThing("query-key", "", "", 0) + })) + .Given(x => x.GivenClaims(claims)) + .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) + .When(x => x.WhenIAddQueriesToTheRequest()) + .Then(x => x.ThenTheResultIsSuccess()) + .And(x => x.ThenTheQueryIsAdded()) + .BDDfy(); + } + + [Fact] + public void should_add_new_queries_to_downstream_request_and_preserve_other_queries() + { + var claims = new List + { + new Claim("test", "data") + }; + + this.Given( + x => x.GivenAClaimToThing(new List + { + new ClaimToThing("query-key", "", "", 0) + })) + .Given(x => x.GivenClaims(claims)) + .And(x => GivenTheDownstreamRequestHasQueryString("?test=1&test=2")) + .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) + .When(x => x.WhenIAddQueriesToTheRequest()) + .Then(x => x.ThenTheResultIsSuccess()) + .And(x => x.ThenTheQueryIsAdded()) + .And(x => TheTheQueryStringIs("?test=1&test=2&query-key=value")) + .BDDfy(); + } + + private void TheTheQueryStringIs(string expected) + { + _downstreamRequest.Query.ShouldBe(expected); + } + + [Fact] + public void should_replace_existing_queries_on_downstream_request() + { + var claims = new List + { + new Claim("test", "data") + }; + + this.Given( + x => x.GivenAClaimToThing(new List + { + new ClaimToThing("query-key", "", "", 0) + })) + .And(x => x.GivenClaims(claims)) + .And(x => x.GivenTheDownstreamRequestHasQueryString("query-key", "initial")) + .And(x => x.GivenTheClaimParserReturns(new OkResponse("value"))) + .When(x => x.WhenIAddQueriesToTheRequest()) + .Then(x => x.ThenTheResultIsSuccess()) + .And(x => x.ThenTheQueryIsAdded()) + .BDDfy(); + } + + [Fact] + public void should_return_error() + { + this.Given( + x => x.GivenAClaimToThing(new List + { + new ClaimToThing("", "", "", 0) + })) + .Given(x => x.GivenClaims(new List())) + .And(x => x.GivenTheClaimParserReturns(new ErrorResponse(new List + { + new AnyError() + }))) + .When(x => x.WhenIAddQueriesToTheRequest()) + .Then(x => x.ThenTheResultIsError()) + .BDDfy(); + } + + private void ThenTheQueryIsAdded() + { + var queries = Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(_downstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString); + var query = queries.First(x => x.Key == "query-key"); + query.Value.First().ShouldBe(_claimValue.Data); + } + + private void GivenAClaimToThing(List configuration) + { + _configuration = configuration; + } + + private void GivenClaims(List claims) + { + _claims = claims; + } + + private void GivenTheDownstreamRequestHasQueryString(string queryString) + { + _request = new HttpRequestMessage(HttpMethod.Post, $"http://my.url/abc{queryString}"); + _downstreamRequest = new DownstreamRequest(_request); + } + + private void GivenTheDownstreamRequestHasQueryString(string key, string value) + { + var newUri = Microsoft.AspNetCore.WebUtilities.QueryHelpers + .AddQueryString(_downstreamRequest.ToHttpRequestMessage().RequestUri.OriginalString, key, value); + + _request.RequestUri = new Uri(newUri); + } + + private void GivenTheClaimParserReturns(Response claimValue) + { + _claimValue = claimValue; + _parser + .Setup( + x => x.GetValue(It.IsAny>(), It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns(_claimValue); - } - - private void WhenIAddQueriesToTheRequest() - { - _result = _addQueriesToRequest.SetQueriesOnDownstreamRequest(_configuration, _claims, _downstreamRequest); - } - - private void ThenTheResultIsSuccess() - { - _result.IsError.ShouldBe(false); - } - - private void ThenTheResultIsError() - { - _result.IsError.ShouldBe(true); - } - - private class AnyError : Error - { + It.IsAny(), + It.IsAny())) + .Returns(_claimValue); + } + + private void WhenIAddQueriesToTheRequest() + { + _result = _addQueriesToRequest.SetQueriesOnDownstreamRequest(_configuration, _claims, _downstreamRequest); + } + + private void ThenTheResultIsSuccess() + { + _result.IsError.ShouldBe(false); + } + + private void ThenTheResultIsError() + { + _result.IsError.ShouldBe(true); + } + + private class AnyError : Error + { public AnyError() - : base("blahh", OcelotErrorCode.UnknownError) - { - } - } - } + : base("blahh", OcelotErrorCode.UnknownError, 404) + { + } + } + } } diff --git a/test/Ocelot.UnitTests/QueryStrings/ClaimsToQueryStringMiddlewareTests.cs b/test/Ocelot.UnitTests/QueryStrings/ClaimsToQueryStringMiddlewareTests.cs index d2716fae..6222199d 100644 --- a/test/Ocelot.UnitTests/QueryStrings/ClaimsToQueryStringMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/QueryStrings/ClaimsToQueryStringMiddlewareTests.cs @@ -1,97 +1,102 @@ -using Ocelot.Middleware; - -namespace Ocelot.UnitTests.QueryStrings -{ - using Microsoft.AspNetCore.Http; - using Moq; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using Ocelot.DownstreamRouteFinder; - using Ocelot.DownstreamRouteFinder.UrlMatcher; - using Ocelot.Logging; - using Ocelot.QueryStrings; - using Ocelot.QueryStrings.Middleware; - using Ocelot.Request.Middleware; - using Ocelot.Responses; - using System.Collections.Generic; - using System.Net.Http; - using System.Security.Claims; - using System.Threading.Tasks; - using TestStack.BDDfy; +using Ocelot.Middleware; + +namespace Ocelot.UnitTests.QueryStrings +{ + using Microsoft.AspNetCore.Http; + using Moq; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Logging; + using Ocelot.QueryStrings; + using Ocelot.QueryStrings.Middleware; + using Ocelot.Request.Middleware; + using Ocelot.Responses; + using System.Collections.Generic; + using System.Net.Http; + using System.Security.Claims; + using System.Threading.Tasks; + using Ocelot.Infrastructure.RequestData; + using TestStack.BDDfy; using Xunit; + using Ocelot.DownstreamRouteFinder.Middleware; - public class ClaimsToQueryStringMiddlewareTests - { - private readonly Mock _addQueries; - private Mock _loggerFactory; - private Mock _logger; - private ClaimsToQueryStringMiddleware _middleware; - private DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; - - public ClaimsToQueryStringMiddlewareTests() - { - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - _loggerFactory = new Mock(); - _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = context => Task.CompletedTask; + public class ClaimsToQueryStringMiddlewareTests + { + private readonly Mock _addQueries; + private Mock _loggerFactory; + private Mock _logger; + private ClaimsToQueryStringMiddleware _middleware; + private RequestDelegate _next; + private HttpContext _httpContext; + private Mock _repo; + + public ClaimsToQueryStringMiddlewareTests() + { + _repo = new Mock(); + _httpContext = new DefaultHttpContext(); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = context => Task.CompletedTask; _addQueries = new Mock(); - _downstreamContext.DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); - _middleware = new ClaimsToQueryStringMiddleware(_next, _loggerFactory.Object, _addQueries.Object); - } - - [Fact] - public void should_call_add_queries_correctly() + _httpContext.Items.UpsertDownstreamRequest(new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com"))); + _middleware = new ClaimsToQueryStringMiddleware(_next, _loggerFactory.Object, _addQueries.Object); + } + + [Fact] + public void should_call_add_queries_correctly() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithClaimsToQueries(new List + { + new ClaimToThing("UserId", "Subject", "", 0) + }) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => x.GivenTheAddHeadersToRequestReturnsOk()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheAddQueriesToRequestIsCalledCorrectly()) + .BDDfy(); + } + + private void WhenICallTheMiddleware() + { + _middleware.Invoke(_httpContext).GetAwaiter().GetResult(); + } + + private void GivenTheAddHeadersToRequestReturnsOk() + { + _addQueries + .Setup(x => x.SetQueriesOnDownstreamRequest( + It.IsAny>(), + It.IsAny>(), + It.IsAny())) + .Returns(new OkResponse()); + } + + private void ThenTheAddQueriesToRequestIsCalledCorrectly() + { + _addQueries + .Verify(x => x.SetQueriesOnDownstreamRequest( + It.IsAny>(), + It.IsAny>(), + _httpContext.Items.DownstreamRequest()), Times.Once); + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithClaimsToQueries(new List - { - new ClaimToThing("UserId", "Subject", "", 0) - }) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); + _httpContext.Items.UpsertTemplatePlaceholderNameAndValues(downstreamRoute.TemplatePlaceholderNameAndValues); - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => x.GivenTheAddHeadersToRequestReturnsOk()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheAddQueriesToRequestIsCalledCorrectly()) - .BDDfy(); - } - - private void WhenICallTheMiddleware() - { - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - } - - private void GivenTheAddHeadersToRequestReturnsOk() - { - _addQueries - .Setup(x => x.SetQueriesOnDownstreamRequest( - It.IsAny>(), - It.IsAny>(), - It.IsAny())) - .Returns(new OkResponse()); - } - - private void ThenTheAddQueriesToRequestIsCalledCorrectly() - { - _addQueries - .Verify(x => x.SetQueriesOnDownstreamRequest( - It.IsAny>(), - It.IsAny>(), - _downstreamContext.DownstreamRequest), Times.Once); - } - - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues; - _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; - } - } -} + _httpContext.Items.UpsertDownstreamReRoute(downstreamRoute.ReRoute.DownstreamReRoute[0]); + } + } +} diff --git a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs index d0f69cec..3d8feda2 100644 --- a/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RateLimit/ClientRateLimitMiddlewareTests.cs @@ -1,179 +1,186 @@ -using Ocelot.Middleware; - -namespace Ocelot.UnitTests.RateLimit -{ - using Microsoft.AspNetCore.Http; - using Microsoft.Extensions.Caching.Memory; - using Moq; - using Ocelot.Configuration; - using Ocelot.Configuration.Builder; - using Ocelot.DownstreamRouteFinder; - using Ocelot.Logging; - using Ocelot.RateLimit; - using Ocelot.RateLimit.Middleware; - using Ocelot.Request.Middleware; - using Shouldly; - using System.Collections.Generic; - using System.IO; - using System.Net.Http; - using System.Threading.Tasks; - using TestStack.BDDfy; - using Xunit; - - public class ClientRateLimitMiddlewareTests - { - private int _responseStatusCode; - private IRateLimitCounterHandler _rateLimitCounterHandler; - private Mock _loggerFactory; - private Mock _logger; - private readonly ClientRateLimitMiddleware _middleware; - private readonly DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; - private readonly string _url; - - public ClientRateLimitMiddlewareTests() - { - _url = "http://localhost:51879"; - var cacheEntryOptions = new MemoryCacheOptions(); - _rateLimitCounterHandler = new MemoryCacheRateLimitCounterHandler(new MemoryCache(cacheEntryOptions)); - var httpContext = new DefaultHttpContext(); - _downstreamContext = new DownstreamContext(httpContext); - _downstreamContext.HttpContext.Response.Body = new FakeStream(); - - _loggerFactory = new Mock(); - _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = context => Task.CompletedTask; - _middleware = new ClientRateLimitMiddleware(_next, _loggerFactory.Object, _rateLimitCounterHandler); - } - - [Fact] - public void should_call_middleware_and_ratelimiting() - { - var upstreamTemplate = new UpstreamPathTemplateBuilder().Build(); - - var downstreamReRoute = new DownstreamReRouteBuilder() - .WithEnableRateLimiting(true) - .WithRateLimitOptions(new RateLimitOptions(true, "ClientId", () => new List(), false, "", "", new RateLimitRule("1s", 100, 3), 429)) - .WithUpstreamHttpMethod(new List { "Get" }) - .WithUpstreamPathTemplate(upstreamTemplate) - .Build(); - - var reRoute = new ReRouteBuilder() - .WithDownstreamReRoute(downstreamReRoute) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build(); - - var downstreamRoute = new DownstreamRoute(new List(), reRoute); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .When(x => x.WhenICallTheMiddlewareMultipleTime(2)) - .Then(x => x.ThenresponseStatusCodeIs200()) - .When(x => x.WhenICallTheMiddlewareMultipleTime(3)) - .Then(x => x.ThenresponseStatusCodeIs429()) - .BDDfy(); - } - - [Fact] - public void should_call_middleware_withWhitelistClient() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(new DownstreamReRouteBuilder() - .WithEnableRateLimiting(true) - .WithRateLimitOptions( - new Ocelot.Configuration.RateLimitOptions(true, "ClientId", () => new List() { "ocelotclient2" }, false, "", "", new RateLimitRule("1s", 100, 3), 429)) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .When(x => x.WhenICallTheMiddlewareWithWhiteClient()) - .Then(x => x.ThenresponseStatusCodeIs200()) - .BDDfy(); - } - - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues; - _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; - } - - private void WhenICallTheMiddlewareMultipleTime(int times) - { - var clientId = "ocelotclient1"; - - for (int i = 0; i < times; i++) - { - var request = new HttpRequestMessage(new HttpMethod("GET"), _url); - request.Headers.Add("ClientId", clientId); - _downstreamContext.DownstreamRequest = new DownstreamRequest(request); - - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - _responseStatusCode = (int)_downstreamContext.HttpContext.Response.StatusCode; - } - } - - private void WhenICallTheMiddlewareWithWhiteClient() - { - var clientId = "ocelotclient2"; - - for (int i = 0; i < 10; i++) - { - var request = new HttpRequestMessage(new HttpMethod("GET"), _url); - request.Headers.Add("ClientId", clientId); - _downstreamContext.DownstreamRequest = new DownstreamRequest(request); - _downstreamContext.HttpContext.Request.Headers.TryAdd("ClientId", clientId); - - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - _responseStatusCode = (int)_downstreamContext.HttpContext.Response.StatusCode; - } - } - - private void ThenresponseStatusCodeIs429() - { - _responseStatusCode.ShouldBe(429); - } - - private void ThenresponseStatusCodeIs200() - { - _responseStatusCode.ShouldBe(200); - } - } - - internal class FakeStream : Stream - { - public override void Flush() - { - //do nothing - //throw new System.NotImplementedException(); - } - - public override int Read(byte[] buffer, int offset, int count) - { - throw new System.NotImplementedException(); - } - - public override long Seek(long offset, SeekOrigin origin) - { - throw new System.NotImplementedException(); - } - - public override void SetLength(long value) - { - throw new System.NotImplementedException(); - } - - public override void Write(byte[] buffer, int offset, int count) - { - //do nothing - } - - public override bool CanRead { get; } - public override bool CanSeek { get; } - public override bool CanWrite => true; - public override long Length { get; } - public override long Position { get; set; } - } -} +using Ocelot.Middleware; + +namespace Ocelot.UnitTests.RateLimit +{ + using Microsoft.AspNetCore.Http; + using Microsoft.Extensions.Caching.Memory; + using Moq; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.Logging; + using Ocelot.RateLimit; + using Ocelot.RateLimit.Middleware; + using Ocelot.Request.Middleware; + using Shouldly; + using System.Collections.Generic; + using System.IO; + using System.Net.Http; + using System.Threading.Tasks; + using Ocelot.Infrastructure.RequestData; + using TestStack.BDDfy; + using Xunit; + using Ocelot.DownstreamRouteFinder.Middleware; + + public class ClientRateLimitMiddlewareTests + { + private int _responseStatusCode; + private IRateLimitCounterHandler _rateLimitCounterHandler; + private Mock _loggerFactory; + private Mock _logger; + private readonly ClientRateLimitMiddleware _middleware; + private RequestDelegate _next; + private DownstreamResponse _downstreamResponse; + private readonly string _url; + + public ClientRateLimitMiddlewareTests() + { + _url = "http://localhost:51879"; + var cacheEntryOptions = new MemoryCacheOptions(); + _rateLimitCounterHandler = new MemoryCacheRateLimitCounterHandler(new MemoryCache(cacheEntryOptions)); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = context => Task.CompletedTask; + _middleware = new ClientRateLimitMiddleware(_next, _loggerFactory.Object, _rateLimitCounterHandler); + } + + [Fact] + public void should_call_middleware_and_ratelimiting() + { + var upstreamTemplate = new UpstreamPathTemplateBuilder().Build(); + + var downstreamReRoute = new DownstreamReRouteBuilder() + .WithEnableRateLimiting(true) + .WithRateLimitOptions(new RateLimitOptions(true, "ClientId", () => new List(), false, "", "", new RateLimitRule("1s", 100, 3), 429)) + .WithUpstreamHttpMethod(new List { "Get" }) + .WithUpstreamPathTemplate(upstreamTemplate) + .Build(); + + var reRoute = new ReRouteBuilder() + .WithDownstreamReRoute(downstreamReRoute) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build(); + + var downstreamRoute = new DownstreamRoute(new List(), reRoute); + + this.Given(x => x.WhenICallTheMiddlewareMultipleTimes(2, downstreamRoute)) + .Then(x => x.ThenThereIsNoDownstreamResponse()) + .When(x => x.WhenICallTheMiddlewareMultipleTimes(3, downstreamRoute)) + .Then(x => x.ThenTheResponseIs429()) + .BDDfy(); + } + + [Fact] + public void should_call_middleware_withWhitelistClient() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithEnableRateLimiting(true) + .WithRateLimitOptions( + new Ocelot.Configuration.RateLimitOptions(true, "ClientId", () => new List() { "ocelotclient2" }, false, "", "", new RateLimitRule("1s", 100, 3), 429)) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + this.Given(x => x.WhenICallTheMiddlewareWithWhiteClient(downstreamRoute)) + .Then(x => x.ThenThereIsNoDownstreamResponse()) + .BDDfy(); + } + + private void WhenICallTheMiddlewareMultipleTimes(int times, DownstreamRoute downstreamRoute) + { + var httpContexts = new List(); + + for (int i = 0; i < times; i++) + { + var httpContext = new DefaultHttpContext(); + httpContext.Response.Body = new FakeStream(); + httpContext.Items.UpsertDownstreamReRoute(downstreamRoute.ReRoute.DownstreamReRoute[0]); + httpContext.Items.UpsertTemplatePlaceholderNameAndValues(downstreamRoute.TemplatePlaceholderNameAndValues); + httpContext.Items.UpsertDownstreamRoute(downstreamRoute); + var clientId = "ocelotclient1"; + var request = new HttpRequestMessage(new HttpMethod("GET"), _url); + httpContext.Items.UpsertDownstreamRequest(new DownstreamRequest(request)); + httpContext.Request.Headers.TryAdd("ClientId", clientId); + httpContexts.Add(httpContext); + } + + foreach (var httpContext in httpContexts) + { + _middleware.Invoke(httpContext).GetAwaiter().GetResult(); + var ds = httpContext.Items.DownstreamResponse(); + _downstreamResponse = ds; + } + } + + private void WhenICallTheMiddlewareWithWhiteClient(DownstreamRoute downstreamRoute) + { + var clientId = "ocelotclient2"; + + for (int i = 0; i < 10; i++) + { + var httpContext = new DefaultHttpContext(); + httpContext.Response.Body = new FakeStream(); + httpContext.Items.UpsertDownstreamReRoute(downstreamRoute.ReRoute.DownstreamReRoute[0]); + httpContext.Items.UpsertTemplatePlaceholderNameAndValues(downstreamRoute.TemplatePlaceholderNameAndValues); + httpContext.Items.UpsertDownstreamRoute(downstreamRoute); + var request = new HttpRequestMessage(new HttpMethod("GET"), _url); + request.Headers.Add("ClientId", clientId); + httpContext.Items.UpsertDownstreamRequest(new DownstreamRequest(request)); + httpContext.Request.Headers.TryAdd("ClientId", clientId); + _middleware.Invoke(httpContext).GetAwaiter().GetResult(); + var ds = httpContext.Items.DownstreamResponse(); + _downstreamResponse = ds; + } + } + + private void ThenTheResponseIs429() + { + var code = (int)_downstreamResponse.StatusCode; + code.ShouldBe(429); + } + + private void ThenThereIsNoDownstreamResponse() + { + _downstreamResponse.ShouldBeNull(); + } + } + + internal class FakeStream : Stream + { + public override void Flush() + { + //do nothing + //throw new System.NotImplementedException(); + } + + public override int Read(byte[] buffer, int offset, int count) + { + throw new System.NotImplementedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new System.NotImplementedException(); + } + + public override void SetLength(long value) + { + throw new System.NotImplementedException(); + } + + public override void Write(byte[] buffer, int offset, int count) + { + //do nothing + } + + public override bool CanRead { get; } + public override bool CanSeek { get; } + public override bool CanWrite => true; + public override long Length { get; } + public override long Position { get; set; } + } +} diff --git a/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs b/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs index 13bf024b..473f119f 100644 --- a/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Request/DownstreamRequestInitialiserMiddlewareTests.cs @@ -15,32 +15,23 @@ using Ocelot.Configuration; using TestStack.BDDfy; using Xunit; + using Ocelot.DownstreamRouteFinder.Middleware; public class DownstreamRequestInitialiserMiddlewareTests { private readonly DownstreamRequestInitialiserMiddleware _middleware; - - private readonly Mock _httpContext; - - private readonly Mock _httpRequest; - - private readonly Mock _next; - + private readonly HttpContext _httpContext; + private readonly Mock _next; private readonly Mock _requestMapper; - private readonly Mock _loggerFactory; - private readonly Mock _logger; - private Response _mappedRequest; - private DownstreamContext _downstreamContext; public DownstreamRequestInitialiserMiddlewareTests() { - _httpContext = new Mock(); - _httpRequest = new Mock(); + _httpContext = new DefaultHttpContext(); _requestMapper = new Mock(); - _next = new Mock(); + _next = new Mock(); _logger = new Mock(); _loggerFactory = new Mock(); @@ -53,8 +44,6 @@ _loggerFactory.Object, _requestMapper.Object, new DownstreamRequestCreator(new FrameworkDescription())); - - _downstreamContext = new DownstreamContext(_httpContext.Object); } [Fact] @@ -97,14 +86,12 @@ private void ThenTheDownstreamRequestMethodIs(string expected) { - _downstreamContext.DownstreamRequest.Method.ShouldBe(expected); + _httpContext.Items.DownstreamRequest().Method.ShouldBe(expected); } private void GivenTheHttpContextContainsARequest() { - _httpContext - .Setup(hc => hc.Request) - .Returns(_httpRequest.Object); + _httpContext.Items.UpsertDownstreamReRoute(new DownstreamReRouteBuilder().Build()); } private void GivenTheMapperWillReturnAMappedRequest() @@ -127,38 +114,38 @@ private void WhenTheMiddlewareIsInvoked() { - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); + _middleware.Invoke(_httpContext).GetAwaiter().GetResult(); } private void ThenTheContexRequestIsMappedToADownstreamRequest() { - _requestMapper.Verify(rm => rm.Map(_httpRequest.Object, _downstreamContext.DownstreamReRoute), Times.Once); + _requestMapper.Verify(rm => rm.Map(_httpContext.Request, _httpContext.Items.DownstreamReRoute()), Times.Once); } private void ThenTheDownstreamRequestIsStored() { - _downstreamContext.DownstreamRequest.ShouldNotBeNull(); + _httpContext.Items.DownstreamRequest().ShouldNotBeNull(); } private void ThenTheDownstreamRequestIsNotStored() { - _downstreamContext.DownstreamRequest.ShouldBeNull(); + _httpContext.Items.DownstreamRequest().ShouldBeNull(); } private void ThenAPipelineErrorIsStored() { - _downstreamContext.IsError.ShouldBeTrue(); - _downstreamContext.Errors.ShouldBe(_mappedRequest.Errors); + _httpContext.Items.Errors().Count.ShouldBeGreaterThan(0); + _httpContext.Items.Errors().ShouldBe(_mappedRequest.Errors); } private void ThenTheNextMiddlewareIsInvoked() { - _next.Verify(n => n(_downstreamContext), Times.Once); + _next.Verify(n => n(_httpContext), Times.Once); } private void ThenTheNextMiddlewareIsNotInvoked() { - _next.Verify(n => n(It.IsAny()), Times.Never); + _next.Verify(n => n(It.IsAny()), Times.Never); } } } diff --git a/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs b/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs index 5aebd7e5..c17f51c9 100644 --- a/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/RequestId/ReRouteRequestIdMiddlewareTests.cs @@ -1,222 +1,223 @@ -namespace Ocelot.UnitTests.RequestId -{ - using Microsoft.AspNetCore.Http; - using Moq; - using Ocelot.Configuration.Builder; - using Ocelot.DownstreamRouteFinder; - using Ocelot.DownstreamRouteFinder.UrlMatcher; - using Ocelot.Infrastructure.RequestData; - using Ocelot.Logging; - using Ocelot.Middleware; - using Ocelot.Request.Middleware; - using Ocelot.RequestId.Middleware; - using Ocelot.Responses; - using Shouldly; - using System; - using System.Collections.Generic; - using System.Linq; - using System.Net.Http; - using System.Threading.Tasks; - using TestStack.BDDfy; - using Xunit; - - public class ReRouteRequestIdMiddlewareTests - { - private readonly HttpRequestMessage _downstreamRequest; - private string _value; - private string _key; - private Mock _loggerFactory; - private Mock _logger; - private readonly ReRouteRequestIdMiddleware _middleware; - private readonly DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; - private readonly Mock _repo; - - public ReRouteRequestIdMiddlewareTests() - { - _downstreamRequest = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); - _repo = new Mock(); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - _loggerFactory = new Mock(); - _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = context => - { - context.HttpContext.Response.Headers.Add("LSRequestId", context.HttpContext.TraceIdentifier); - return Task.CompletedTask; - }; - _middleware = new ReRouteRequestIdMiddleware(_next, _loggerFactory.Object, _repo.Object); - _downstreamContext.DownstreamRequest = new DownstreamRequest(_downstreamRequest); - } - - [Fact] - public void should_pass_down_request_id_from_upstream_request() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithRequestIdKey("LSRequestId") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); - - var requestId = Guid.NewGuid().ToString(); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => GivenThereIsNoGlobalRequestId()) - .And(x => x.GivenTheRequestIdIsAddedToTheRequest("LSRequestId", requestId)) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheTraceIdIs(requestId)) - .BDDfy(); - } - - [Fact] - public void should_add_request_id_when_not_on_upstream_request() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithRequestIdKey("LSRequestId") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => GivenThereIsNoGlobalRequestId()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheTraceIdIsAnything()) - .BDDfy(); - } - - [Fact] - public void should_add_request_id_scoped_repo_for_logging_later() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithRequestIdKey("LSRequestId") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); - - var requestId = Guid.NewGuid().ToString(); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => GivenThereIsNoGlobalRequestId()) - .And(x => x.GivenTheRequestIdIsAddedToTheRequest("LSRequestId", requestId)) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheTraceIdIs(requestId)) - .And(x => ThenTheRequestIdIsSaved()) - .BDDfy(); - } - - [Fact] - public void should_update_request_id_scoped_repo_for_logging_later() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithRequestIdKey("LSRequestId") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); - - var requestId = Guid.NewGuid().ToString(); - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => GivenTheRequestIdWasSetGlobally()) - .And(x => x.GivenTheRequestIdIsAddedToTheRequest("LSRequestId", requestId)) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheTraceIdIs(requestId)) - .And(x => ThenTheRequestIdIsUpdated()) - .BDDfy(); - } - - [Fact] - public void should_not_update_if_global_request_id_is_same_as_re_route_request_id() - { - var downstreamRoute = new DownstreamRoute(new List(), - new ReRouteBuilder() - .WithDownstreamReRoute(new DownstreamReRouteBuilder() - .WithDownstreamPathTemplate("any old string") - .WithRequestIdKey("LSRequestId") - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()) - .WithUpstreamHttpMethod(new List { "Get" }) - .Build()); - - var requestId = "alreadyset"; - - this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) - .And(x => GivenTheRequestIdWasSetGlobally()) - .And(x => x.GivenTheRequestIdIsAddedToTheRequest("LSRequestId", requestId)) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheTraceIdIs(requestId)) - .And(x => ThenTheRequestIdIsNotUpdated()) - .BDDfy(); - } - - private void WhenICallTheMiddleware() - { - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - } - - private void GivenThereIsNoGlobalRequestId() - { - _repo.Setup(x => x.Get("RequestId")).Returns(new OkResponse(null)); - } - - private void GivenTheRequestIdWasSetGlobally() - { - _repo.Setup(x => x.Get("RequestId")).Returns(new OkResponse("alreadyset")); - } - - private void ThenTheRequestIdIsSaved() - { - _repo.Verify(x => x.Add("RequestId", _value), Times.Once); - } - - private void ThenTheRequestIdIsUpdated() - { - _repo.Verify(x => x.Update("RequestId", _value), Times.Once); - } - - private void ThenTheRequestIdIsNotUpdated() - { - _repo.Verify(x => x.Update("RequestId", _value), Times.Never); - } - - private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) - { - _downstreamContext.TemplatePlaceholderNameAndValues = downstreamRoute.TemplatePlaceholderNameAndValues; - _downstreamContext.DownstreamReRoute = downstreamRoute.ReRoute.DownstreamReRoute[0]; - } - - private void GivenTheRequestIdIsAddedToTheRequest(string key, string value) - { - _key = key; - _value = value; - _downstreamContext.HttpContext.Request.Headers.TryAdd(_key, _value); - } - - private void ThenTheTraceIdIsAnything() - { - _downstreamContext.HttpContext.Response.Headers.TryGetValue("LSRequestId", out var value); - value.First().ShouldNotBeNullOrEmpty(); - } - - private void ThenTheTraceIdIs(string expected) - { - _downstreamContext.HttpContext.Response.Headers.TryGetValue("LSRequestId", out var value); - value.First().ShouldBe(expected); - } - } +namespace Ocelot.UnitTests.RequestId +{ + using Microsoft.AspNetCore.Http; + using Moq; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder; + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.DownstreamRouteFinder.UrlMatcher; + using Ocelot.Infrastructure.RequestData; + using Ocelot.Logging; + using Ocelot.Middleware; + using Ocelot.Request.Middleware; + using Ocelot.RequestId.Middleware; + using Ocelot.Responses; + using Shouldly; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + using TestStack.BDDfy; + using Xunit; + + public class ReRouteRequestIdMiddlewareTests + { + private readonly HttpRequestMessage _downstreamRequest; + private string _value; + private string _key; + private Mock _loggerFactory; + private Mock _logger; + private readonly ReRouteRequestIdMiddleware _middleware; + private RequestDelegate _next; + private readonly Mock _repo; + private HttpContext _httpContext; + public ReRouteRequestIdMiddlewareTests() + { + _httpContext = new DefaultHttpContext(); + _downstreamRequest = new HttpRequestMessage(HttpMethod.Get, "http://test.com"); + _repo = new Mock(); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = context => + { + _httpContext.Response.Headers.Add("LSRequestId", _httpContext.TraceIdentifier); + return Task.CompletedTask; + }; + _middleware = new ReRouteRequestIdMiddleware(_next, _loggerFactory.Object, _repo.Object); + _httpContext.Items.UpsertDownstreamRequest(new DownstreamRequest(_downstreamRequest)); + } + + [Fact] + public void should_pass_down_request_id_from_upstream_request() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithRequestIdKey("LSRequestId") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + var requestId = Guid.NewGuid().ToString(); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => GivenThereIsNoGlobalRequestId()) + .And(x => x.GivenTheRequestIdIsAddedToTheRequest("LSRequestId", requestId)) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheTraceIdIs(requestId)) + .BDDfy(); + } + + [Fact] + public void should_add_request_id_when_not_on_upstream_request() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithRequestIdKey("LSRequestId") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => GivenThereIsNoGlobalRequestId()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheTraceIdIsAnything()) + .BDDfy(); + } + + [Fact] + public void should_add_request_id_scoped_repo_for_logging_later() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithRequestIdKey("LSRequestId") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + var requestId = Guid.NewGuid().ToString(); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => GivenThereIsNoGlobalRequestId()) + .And(x => x.GivenTheRequestIdIsAddedToTheRequest("LSRequestId", requestId)) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheTraceIdIs(requestId)) + .And(x => ThenTheRequestIdIsSaved()) + .BDDfy(); + } + + [Fact] + public void should_update_request_id_scoped_repo_for_logging_later() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithRequestIdKey("LSRequestId") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + var requestId = Guid.NewGuid().ToString(); + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => GivenTheRequestIdWasSetGlobally()) + .And(x => x.GivenTheRequestIdIsAddedToTheRequest("LSRequestId", requestId)) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheTraceIdIs(requestId)) + .And(x => ThenTheRequestIdIsUpdated()) + .BDDfy(); + } + + [Fact] + public void should_not_update_if_global_request_id_is_same_as_re_route_request_id() + { + var downstreamRoute = new DownstreamRoute(new List(), + new ReRouteBuilder() + .WithDownstreamReRoute(new DownstreamReRouteBuilder() + .WithDownstreamPathTemplate("any old string") + .WithRequestIdKey("LSRequestId") + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()) + .WithUpstreamHttpMethod(new List { "Get" }) + .Build()); + + var requestId = "alreadyset"; + + this.Given(x => x.GivenTheDownStreamRouteIs(downstreamRoute)) + .And(x => GivenTheRequestIdWasSetGlobally()) + .And(x => x.GivenTheRequestIdIsAddedToTheRequest("LSRequestId", requestId)) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheTraceIdIs(requestId)) + .And(x => ThenTheRequestIdIsNotUpdated()) + .BDDfy(); + } + + private void WhenICallTheMiddleware() + { + _middleware.Invoke(_httpContext).GetAwaiter().GetResult(); + } + + private void GivenThereIsNoGlobalRequestId() + { + _repo.Setup(x => x.Get("RequestId")).Returns(new OkResponse(null)); + } + + private void GivenTheRequestIdWasSetGlobally() + { + _repo.Setup(x => x.Get("RequestId")).Returns(new OkResponse("alreadyset")); + } + + private void ThenTheRequestIdIsSaved() + { + _repo.Verify(x => x.Add("RequestId", _value), Times.Once); + } + + private void ThenTheRequestIdIsUpdated() + { + _repo.Verify(x => x.Update("RequestId", _value), Times.Once); + } + + private void ThenTheRequestIdIsNotUpdated() + { + _repo.Verify(x => x.Update("RequestId", _value), Times.Never); + } + + private void GivenTheDownStreamRouteIs(DownstreamRoute downstreamRoute) + { + _httpContext.Items.UpsertTemplatePlaceholderNameAndValues(downstreamRoute.TemplatePlaceholderNameAndValues); + + _httpContext.Items.UpsertDownstreamReRoute(downstreamRoute.ReRoute.DownstreamReRoute[0]); + } + + private void GivenTheRequestIdIsAddedToTheRequest(string key, string value) + { + _key = key; + _value = value; + _httpContext.Request.Headers.TryAdd(_key, _value); + } + + private void ThenTheTraceIdIsAnything() + { + _httpContext.Response.Headers.TryGetValue("LSRequestId", out var value); + value.First().ShouldNotBeNullOrEmpty(); + } + + private void ThenTheTraceIdIs(string expected) + { + _httpContext.Response.Headers.TryGetValue("LSRequestId", out var value); + value.First().ShouldBe(expected); + } + } } diff --git a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs index c8dbd4a2..61656838 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientBuilderTests.cs @@ -1,449 +1,446 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Logging; -using Ocelot.Middleware; -using Ocelot.Request.Middleware; -using Ocelot.Requester; -using Ocelot.Responses; -using Shouldly; -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Requester -{ - public class HttpClientBuilderTests : IDisposable - { - private HttpClientBuilder _builder; - private readonly Mock _factory; - private IHttpClient _httpClient; - private HttpResponseMessage _response; - private DownstreamContext _context; - private readonly Mock _cacheHandlers; - private readonly Mock _logger; - private int _count; - private IWebHost _host; - private IHttpClient _againHttpClient; - private IHttpClient _firstHttpClient; - private MemoryHttpClientCache _realCache; - - public HttpClientBuilderTests() - { - _cacheHandlers = new Mock(); - _logger = new Mock(); - _factory = new Mock(); - _builder = new HttpClientBuilder(_factory.Object, _cacheHandlers.Object, _logger.Object); - } - - [Fact] - public void should_build_http_client() - { - var qosOptions = new QoSOptionsBuilder() - .Build(); - - var reRoute = new DownstreamReRouteBuilder() - .WithQosOptions(qosOptions) - .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true, int.MaxValue)) - .WithLoadBalancerKey("") - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) - .WithQosOptions(new QoSOptionsBuilder().Build()) - .Build(); - - this.Given(x => GivenTheFactoryReturns()) - .And(x => GivenARequest(reRoute)) - .When(x => WhenIBuild()) - .Then(x => ThenTheHttpClientShouldNotBeNull()) - .BDDfy(); - } - - [Fact] - public void should_get_from_cache() - { - var qosOptions = new QoSOptionsBuilder() - .Build(); - - var reRoute = new DownstreamReRouteBuilder() - .WithQosOptions(qosOptions) - .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true, int.MaxValue)) - .WithLoadBalancerKey("") - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) - .WithQosOptions(new QoSOptionsBuilder().Build()) - .Build(); - - this.Given(x => GivenARealCache()) - .And(x => GivenTheFactoryReturns()) - .And(x => GivenARequest(reRoute)) - .And(x => WhenIBuildTheFirstTime()) - .And(x => WhenISave()) - .And(x => WhenIBuildAgain()) - .And(x => WhenISave()) - .When(x => WhenIBuildAgain()) - .Then(x => ThenTheHttpClientIsFromTheCache()) - .BDDfy(); - } - - [Fact] - public void should_get_from_cache_with_different_query_string() - { - var qosOptions = new QoSOptionsBuilder() - .Build(); - - var reRoute = new DownstreamReRouteBuilder() - .WithQosOptions(qosOptions) - .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true, int.MaxValue)) - .WithLoadBalancerKey("") - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) - .WithQosOptions(new QoSOptionsBuilder().Build()) - .Build(); - - this.Given(x => GivenARealCache()) - .And(x => GivenTheFactoryReturns()) - .And(x => GivenARequest(reRoute, "http://wwww.someawesomewebsite.com/woot?badman=1")) - .And(x => WhenIBuildTheFirstTime()) - .And(x => WhenISave()) - .And(x => WhenIBuildAgain()) - .And(x => GivenARequest(reRoute, "http://wwww.someawesomewebsite.com/woot?badman=2")) - .And(x => WhenISave()) - .When(x => WhenIBuildAgain()) - .Then(x => ThenTheHttpClientIsFromTheCache()) - .BDDfy(); - } - - [Fact] - public void should_not_get_from_cache_with_different_query_string() - { - var qosOptions = new QoSOptionsBuilder() - .Build(); - - var reRouteA = new DownstreamReRouteBuilder() - .WithQosOptions(qosOptions) - .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true, int.MaxValue)) - .WithLoadBalancerKey("") - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithContainsQueryString(true).WithOriginalValue("").Build()) - .WithQosOptions(new QoSOptionsBuilder().Build()) - .Build(); - - var reRouteB = new DownstreamReRouteBuilder() - .WithQosOptions(qosOptions) - .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true, int.MaxValue)) - .WithLoadBalancerKey("") - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithContainsQueryString(true).WithOriginalValue("").Build()) - .WithQosOptions(new QoSOptionsBuilder().Build()) - .Build(); - - this.Given(x => GivenARealCache()) - .And(x => GivenTheFactoryReturns()) - .And(x => GivenARequest(reRouteA, "http://wwww.someawesomewebsite.com/woot?badman=1")) - .And(x => WhenIBuildTheFirstTime()) - .And(x => WhenISave()) - .And(x => WhenIBuildAgain()) - .And(x => GivenARequest(reRouteB, "http://wwww.someawesomewebsite.com/woot?badman=2")) - .And(x => WhenISave()) - .When(x => WhenIBuildAgain()) - .Then(x => ThenTheHttpClientIsNotFromTheCache()) - .BDDfy(); - } - - [Fact] - public void should_log_if_ignoring_ssl_errors() - { - var qosOptions = new QoSOptionsBuilder() - .Build(); - - var reRoute = new DownstreamReRouteBuilder() - .WithQosOptions(qosOptions) - .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true, int.MaxValue)) - .WithLoadBalancerKey("") - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) - .WithQosOptions(new QoSOptionsBuilder().Build()) - .WithDangerousAcceptAnyServerCertificateValidator(true) - .Build(); - - this.Given(x => GivenTheFactoryReturns()) - .And(x => GivenARequest(reRoute)) - .When(x => WhenIBuild()) - .Then(x => ThenTheHttpClientShouldNotBeNull()) - .Then(x => ThenTheDangerousAcceptAnyServerCertificateValidatorWarningIsLogged()) - .BDDfy(); - } - - [Fact] - public void should_call_delegating_handlers_in_order() - { - var qosOptions = new QoSOptionsBuilder() - .Build(); - - var reRoute = new DownstreamReRouteBuilder() - .WithQosOptions(qosOptions) - .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true, int.MaxValue)) - .WithLoadBalancerKey("") - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) - .WithQosOptions(new QoSOptionsBuilder().Build()) - .Build(); - - var fakeOne = new FakeDelegatingHandler(); - var fakeTwo = new FakeDelegatingHandler(); - - var handlers = new List>() - { - () => fakeOne, - () => fakeTwo - }; - - this.Given(x => GivenTheFactoryReturns(handlers)) - .And(x => GivenARequest(reRoute)) - .And(x => WhenIBuild()) - .When(x => WhenICallTheClient()) - .Then(x => ThenTheFakeAreHandledInOrder(fakeOne, fakeTwo)) - .And(x => ThenSomethingIsReturned()) - .BDDfy(); - } - - [Fact] - public void should_re_use_cookies_from_container() - { - var qosOptions = new QoSOptionsBuilder() - .Build(); - - var reRoute = new DownstreamReRouteBuilder() - .WithQosOptions(qosOptions) - .WithHttpHandlerOptions(new HttpHandlerOptions(false, true, false, true, int.MaxValue)) - .WithLoadBalancerKey("") - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) - .WithQosOptions(new QoSOptionsBuilder().Build()) - .Build(); - - this.Given(_ => GivenADownstreamService()) - .And(_ => GivenARequest(reRoute)) - .And(_ => GivenTheFactoryReturnsNothing()) - .And(_ => WhenIBuild()) - .And(_ => WhenICallTheClient("http://localhost:5003")) - .And(_ => ThenTheCookieIsSet()) - .And(_ => GivenTheClientIsCached()) - .And(_ => WhenIBuild()) - .When(_ => WhenICallTheClient("http://localhost:5003")) - .Then(_ => ThenTheResponseIsOk()) - .BDDfy(); - } - - [Theory] - [InlineData("GET")] - [InlineData("POST")] - [InlineData("PUT")] - [InlineData("DELETE")] - [InlineData("PATCH")] - public void should_add_verb_to_cache_key(string verb) - { - var downstreamUrl = "http://localhost:5012/"; - - var method = new HttpMethod(verb); - - var qosOptions = new QoSOptionsBuilder() - .Build(); - - var reRoute = new DownstreamReRouteBuilder() - .WithQosOptions(qosOptions) - .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true, int.MaxValue)) - .WithLoadBalancerKey("") - .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) - .WithQosOptions(new QoSOptionsBuilder().Build()) - .Build(); - - this.Given(_ => GivenADownstreamService()) - .And(_ => GivenARequestWithAUrlAndMethod(reRoute, downstreamUrl, method)) - .And(_ => GivenTheFactoryReturnsNothing()) - .And(_ => WhenIBuild()) - .And(_ => GivenCacheIsCalledWithExpectedKey($"{method.ToString()}:{downstreamUrl}")) - .BDDfy(); - } - - private void GivenARealCache() - { - _realCache = new MemoryHttpClientCache(); - _builder = new HttpClientBuilder(_factory.Object, _realCache, _logger.Object); - } - - private void ThenTheHttpClientIsFromTheCache() - { - _againHttpClient.ShouldBe(_firstHttpClient); - } - - private void ThenTheHttpClientIsNotFromTheCache() - { - _againHttpClient.ShouldNotBe(_firstHttpClient); - } - - private void WhenISave() - { - _builder.Save(); - } - - private void GivenCacheIsCalledWithExpectedKey(string expectedKey) - { - _cacheHandlers.Verify(x => x.Get(It.IsAny()), Times.Once); - } - - private void ThenTheDangerousAcceptAnyServerCertificateValidatorWarningIsLogged() - { - _logger.Verify(x => x.LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamReRoute, UpstreamPathTemplate: {_context.DownstreamReRoute.UpstreamPathTemplate}, DownstreamPathTemplate: {_context.DownstreamReRoute.DownstreamPathTemplate}"), Times.Once); - } - - private void GivenTheClientIsCached() - { - _cacheHandlers.Setup(x => x.Get(It.IsAny())).Returns(_httpClient); - } - - private void ThenTheCookieIsSet() - { - _response.Headers.TryGetValues("Set-Cookie", out var test).ShouldBeTrue(); - } - - private void WhenICallTheClient(string url) - { - _response = _httpClient - .SendAsync(new HttpRequestMessage(HttpMethod.Get, url)) - .GetAwaiter() - .GetResult(); - } - - private void ThenTheResponseIsOk() - { - _response.StatusCode.ShouldBe(HttpStatusCode.OK); - } - - private void GivenADownstreamService() - { - _host = new WebHostBuilder() - .UseUrls("http://localhost:5003") - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseIISIntegration() - .Configure(app => - { - app.Run(context => - { - if (_count == 0) - { - context.Response.Cookies.Append("test", "0"); - context.Response.StatusCode = 200; - _count++; - return Task.CompletedTask; - } - - if (_count == 1) - { - if (context.Request.Cookies.TryGetValue("test", out var cookieValue) || context.Request.Headers.TryGetValue("Set-Cookie", out var headerValue)) - { - context.Response.StatusCode = 200; - return Task.CompletedTask; - } - - context.Response.StatusCode = 500; - } - - return Task.CompletedTask; - }); - }) - .Build(); - - _host.Start(); - } - - private void GivenARequest(DownstreamReRoute downstream) - { - GivenARequest(downstream, "http://localhost:5003"); - } - - private void GivenARequest(DownstreamReRoute downstream, string downstreamUrl) - { - GivenARequestWithAUrlAndMethod(downstream, downstreamUrl, HttpMethod.Get); - } - - private void GivenARequestWithAUrlAndMethod(DownstreamReRoute downstream, string url, HttpMethod method) - { - var context = new DownstreamContext(new DefaultHttpContext()) - { - DownstreamReRoute = downstream, - DownstreamRequest = new DownstreamRequest(new HttpRequestMessage() { RequestUri = new Uri(url), Method = method }), - }; - - _context = context; - } - - private void ThenSomethingIsReturned() - { - _response.ShouldNotBeNull(); - } - - private void WhenICallTheClient() - { - _response = _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "http://test.com")).GetAwaiter().GetResult(); - } - - private void ThenTheFakeAreHandledInOrder(FakeDelegatingHandler fakeOne, FakeDelegatingHandler fakeTwo) - { - fakeOne.TimeCalled.ShouldBeGreaterThan(fakeTwo.TimeCalled); - } - - private void GivenTheFactoryReturns() - { - var handlers = new List>() { () => new FakeDelegatingHandler() }; - - _factory - .Setup(x => x.Get(It.IsAny())) - .Returns(new OkResponse>>(handlers)); - } - - private void GivenTheFactoryReturnsNothing() - { - var handlers = new List>(); - - _factory - .Setup(x => x.Get(It.IsAny())) - .Returns(new OkResponse>>(handlers)); - } - - private void GivenTheFactoryReturns(List> handlers) - { - _factory - .Setup(x => x.Get(It.IsAny())) - .Returns(new OkResponse>>(handlers)); - } - - private void WhenIBuild() - { - _httpClient = _builder.Create(_context); - } - - private void WhenIBuildTheFirstTime() - { - _firstHttpClient = _builder.Create(_context); - } - - private void WhenIBuildAgain() - { - _builder = new HttpClientBuilder(_factory.Object, _realCache, _logger.Object); - _againHttpClient = _builder.Create(_context); - } - - private void ThenTheHttpClientShouldNotBeNull() - { - _httpClient.ShouldNotBeNull(); - } - - public void Dispose() - { - _response?.Dispose(); - _host?.Dispose(); - } - } -} +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.DownstreamRouteFinder.Middleware; +using Ocelot.Logging; +using Ocelot.Middleware; +using Ocelot.Request.Middleware; +using Ocelot.Requester; +using Ocelot.Responses; +using Shouldly; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Requester +{ + public class HttpClientBuilderTests : IDisposable + { + private HttpClientBuilder _builder; + private readonly Mock _factory; + private IHttpClient _httpClient; + private HttpResponseMessage _response; + private HttpContext _context; + private readonly Mock _cacheHandlers; + private readonly Mock _logger; + private int _count; + private IWebHost _host; + private IHttpClient _againHttpClient; + private IHttpClient _firstHttpClient; + private MemoryHttpClientCache _realCache; + + public HttpClientBuilderTests() + { + _cacheHandlers = new Mock(); + _logger = new Mock(); + _factory = new Mock(); + _builder = new HttpClientBuilder(_factory.Object, _cacheHandlers.Object, _logger.Object); + } + + [Fact] + public void should_build_http_client() + { + var qosOptions = new QoSOptionsBuilder() + .Build(); + + var reRoute = new DownstreamReRouteBuilder() + .WithQosOptions(qosOptions) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true, int.MaxValue)) + .WithLoadBalancerKey("") + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) + .WithQosOptions(new QoSOptionsBuilder().Build()) + .Build(); + + this.Given(x => GivenTheFactoryReturns()) + .And(x => GivenARequest(reRoute)) + .When(x => WhenIBuild()) + .Then(x => ThenTheHttpClientShouldNotBeNull()) + .BDDfy(); + } + + [Fact] + public void should_get_from_cache() + { + var qosOptions = new QoSOptionsBuilder() + .Build(); + + var reRoute = new DownstreamReRouteBuilder() + .WithQosOptions(qosOptions) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true, int.MaxValue)) + .WithLoadBalancerKey("") + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) + .WithQosOptions(new QoSOptionsBuilder().Build()) + .Build(); + + this.Given(x => GivenARealCache()) + .And(x => GivenTheFactoryReturns()) + .And(x => GivenARequest(reRoute)) + .And(x => WhenIBuildTheFirstTime()) + .And(x => WhenISave()) + .And(x => WhenIBuildAgain()) + .And(x => WhenISave()) + .When(x => WhenIBuildAgain()) + .Then(x => ThenTheHttpClientIsFromTheCache()) + .BDDfy(); + } + + [Fact] + public void should_get_from_cache_with_different_query_string() + { + var qosOptions = new QoSOptionsBuilder() + .Build(); + + var reRoute = new DownstreamReRouteBuilder() + .WithQosOptions(qosOptions) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true, int.MaxValue)) + .WithLoadBalancerKey("") + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) + .WithQosOptions(new QoSOptionsBuilder().Build()) + .Build(); + + this.Given(x => GivenARealCache()) + .And(x => GivenTheFactoryReturns()) + .And(x => GivenARequest(reRoute, "http://wwww.someawesomewebsite.com/woot?badman=1")) + .And(x => WhenIBuildTheFirstTime()) + .And(x => WhenISave()) + .And(x => WhenIBuildAgain()) + .And(x => GivenARequest(reRoute, "http://wwww.someawesomewebsite.com/woot?badman=2")) + .And(x => WhenISave()) + .When(x => WhenIBuildAgain()) + .Then(x => ThenTheHttpClientIsFromTheCache()) + .BDDfy(); + } + + [Fact] + public void should_not_get_from_cache_with_different_query_string() + { + var qosOptions = new QoSOptionsBuilder() + .Build(); + + var reRouteA = new DownstreamReRouteBuilder() + .WithQosOptions(qosOptions) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true, int.MaxValue)) + .WithLoadBalancerKey("") + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithContainsQueryString(true).WithOriginalValue("").Build()) + .WithQosOptions(new QoSOptionsBuilder().Build()) + .Build(); + + var reRouteB = new DownstreamReRouteBuilder() + .WithQosOptions(qosOptions) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true, int.MaxValue)) + .WithLoadBalancerKey("") + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithContainsQueryString(true).WithOriginalValue("").Build()) + .WithQosOptions(new QoSOptionsBuilder().Build()) + .Build(); + + this.Given(x => GivenARealCache()) + .And(x => GivenTheFactoryReturns()) + .And(x => GivenARequest(reRouteA, "http://wwww.someawesomewebsite.com/woot?badman=1")) + .And(x => WhenIBuildTheFirstTime()) + .And(x => WhenISave()) + .And(x => WhenIBuildAgain()) + .And(x => GivenARequest(reRouteB, "http://wwww.someawesomewebsite.com/woot?badman=2")) + .And(x => WhenISave()) + .When(x => WhenIBuildAgain()) + .Then(x => ThenTheHttpClientIsNotFromTheCache()) + .BDDfy(); + } + + [Fact] + public void should_log_if_ignoring_ssl_errors() + { + var qosOptions = new QoSOptionsBuilder() + .Build(); + + var reRoute = new DownstreamReRouteBuilder() + .WithQosOptions(qosOptions) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true, int.MaxValue)) + .WithLoadBalancerKey("") + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) + .WithQosOptions(new QoSOptionsBuilder().Build()) + .WithDangerousAcceptAnyServerCertificateValidator(true) + .Build(); + + this.Given(x => GivenTheFactoryReturns()) + .And(x => GivenARequest(reRoute)) + .When(x => WhenIBuild()) + .Then(x => ThenTheHttpClientShouldNotBeNull()) + .Then(x => ThenTheDangerousAcceptAnyServerCertificateValidatorWarningIsLogged()) + .BDDfy(); + } + + [Fact] + public void should_call_delegating_handlers_in_order() + { + var qosOptions = new QoSOptionsBuilder() + .Build(); + + var reRoute = new DownstreamReRouteBuilder() + .WithQosOptions(qosOptions) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true, int.MaxValue)) + .WithLoadBalancerKey("") + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) + .WithQosOptions(new QoSOptionsBuilder().Build()) + .Build(); + + var fakeOne = new FakeDelegatingHandler(); + var fakeTwo = new FakeDelegatingHandler(); + + var handlers = new List>() + { + () => fakeOne, + () => fakeTwo + }; + + this.Given(x => GivenTheFactoryReturns(handlers)) + .And(x => GivenARequest(reRoute)) + .And(x => WhenIBuild()) + .When(x => WhenICallTheClient()) + .Then(x => ThenTheFakeAreHandledInOrder(fakeOne, fakeTwo)) + .And(x => ThenSomethingIsReturned()) + .BDDfy(); + } + + [Fact] + public void should_re_use_cookies_from_container() + { + var qosOptions = new QoSOptionsBuilder() + .Build(); + + var reRoute = new DownstreamReRouteBuilder() + .WithQosOptions(qosOptions) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, true, false, true, int.MaxValue)) + .WithLoadBalancerKey("") + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) + .WithQosOptions(new QoSOptionsBuilder().Build()) + .Build(); + + this.Given(_ => GivenADownstreamService()) + .And(_ => GivenARequest(reRoute)) + .And(_ => GivenTheFactoryReturnsNothing()) + .And(_ => WhenIBuild()) + .And(_ => WhenICallTheClient("http://localhost:5003")) + .And(_ => ThenTheCookieIsSet()) + .And(_ => GivenTheClientIsCached()) + .And(_ => WhenIBuild()) + .When(_ => WhenICallTheClient("http://localhost:5003")) + .Then(_ => ThenTheResponseIsOk()) + .BDDfy(); + } + + [Theory] + [InlineData("GET")] + [InlineData("POST")] + [InlineData("PUT")] + [InlineData("DELETE")] + [InlineData("PATCH")] + public void should_add_verb_to_cache_key(string verb) + { + var downstreamUrl = "http://localhost:5012/"; + + var method = new HttpMethod(verb); + + var qosOptions = new QoSOptionsBuilder() + .Build(); + + var reRoute = new DownstreamReRouteBuilder() + .WithQosOptions(qosOptions) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true, int.MaxValue)) + .WithLoadBalancerKey("") + .WithUpstreamPathTemplate(new UpstreamPathTemplateBuilder().WithOriginalValue("").Build()) + .WithQosOptions(new QoSOptionsBuilder().Build()) + .Build(); + + this.Given(_ => GivenADownstreamService()) + .And(_ => GivenARequestWithAUrlAndMethod(reRoute, downstreamUrl, method)) + .And(_ => GivenTheFactoryReturnsNothing()) + .And(_ => WhenIBuild()) + .And(_ => GivenCacheIsCalledWithExpectedKey($"{method.ToString()}:{downstreamUrl}")) + .BDDfy(); + } + + private void GivenARealCache() + { + _realCache = new MemoryHttpClientCache(); + _builder = new HttpClientBuilder(_factory.Object, _realCache, _logger.Object); + } + + private void ThenTheHttpClientIsFromTheCache() + { + _againHttpClient.ShouldBe(_firstHttpClient); + } + + private void ThenTheHttpClientIsNotFromTheCache() + { + _againHttpClient.ShouldNotBe(_firstHttpClient); + } + + private void WhenISave() + { + _builder.Save(); + } + + private void GivenCacheIsCalledWithExpectedKey(string expectedKey) + { + _cacheHandlers.Verify(x => x.Get(It.IsAny()), Times.Once); + } + + private void ThenTheDangerousAcceptAnyServerCertificateValidatorWarningIsLogged() + { + _logger.Verify(x => x.LogWarning($"You have ignored all SSL warnings by using DangerousAcceptAnyServerCertificateValidator for this DownstreamReRoute, UpstreamPathTemplate: {_context.Items.DownstreamReRoute().UpstreamPathTemplate}, DownstreamPathTemplate: {_context.Items.DownstreamReRoute().DownstreamPathTemplate}"), Times.Once); + } + + private void GivenTheClientIsCached() + { + _cacheHandlers.Setup(x => x.Get(It.IsAny())).Returns(_httpClient); + } + + private void ThenTheCookieIsSet() + { + _response.Headers.TryGetValues("Set-Cookie", out var test).ShouldBeTrue(); + } + + private void WhenICallTheClient(string url) + { + _response = _httpClient + .SendAsync(new HttpRequestMessage(HttpMethod.Get, url)) + .GetAwaiter() + .GetResult(); + } + + private void ThenTheResponseIsOk() + { + _response.StatusCode.ShouldBe(HttpStatusCode.OK); + } + + private void GivenADownstreamService() + { + _host = new WebHostBuilder() + .UseUrls("http://localhost:5003") + .UseKestrel() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseIISIntegration() + .Configure(app => + { + app.Run(context => + { + if (_count == 0) + { + context.Response.Cookies.Append("test", "0"); + context.Response.StatusCode = 200; + _count++; + return Task.CompletedTask; + } + + if (_count == 1) + { + if (context.Request.Cookies.TryGetValue("test", out var cookieValue) || context.Request.Headers.TryGetValue("Set-Cookie", out var headerValue)) + { + context.Response.StatusCode = 200; + return Task.CompletedTask; + } + + context.Response.StatusCode = 500; + } + + return Task.CompletedTask; + }); + }) + .Build(); + + _host.Start(); + } + + private void GivenARequest(DownstreamReRoute downstream) + { + GivenARequest(downstream, "http://localhost:5003"); + } + + private void GivenARequest(DownstreamReRoute downstream, string downstreamUrl) + { + GivenARequestWithAUrlAndMethod(downstream, downstreamUrl, HttpMethod.Get); + } + + private void GivenARequestWithAUrlAndMethod(DownstreamReRoute downstream, string url, HttpMethod method) + { + _context = new DefaultHttpContext(); + _context.Items.UpsertDownstreamReRoute(downstream); + _context.Items.UpsertDownstreamRequest(new DownstreamRequest(new HttpRequestMessage() { RequestUri = new Uri(url), Method = method })); + } + + private void ThenSomethingIsReturned() + { + _response.ShouldNotBeNull(); + } + + private void WhenICallTheClient() + { + _response = _httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, "http://test.com")).GetAwaiter().GetResult(); + } + + private void ThenTheFakeAreHandledInOrder(FakeDelegatingHandler fakeOne, FakeDelegatingHandler fakeTwo) + { + fakeOne.TimeCalled.ShouldBeGreaterThan(fakeTwo.TimeCalled); + } + + private void GivenTheFactoryReturns() + { + var handlers = new List>() { () => new FakeDelegatingHandler() }; + + _factory + .Setup(x => x.Get(It.IsAny())) + .Returns(new OkResponse>>(handlers)); + } + + private void GivenTheFactoryReturnsNothing() + { + var handlers = new List>(); + + _factory + .Setup(x => x.Get(It.IsAny())) + .Returns(new OkResponse>>(handlers)); + } + + private void GivenTheFactoryReturns(List> handlers) + { + _factory + .Setup(x => x.Get(It.IsAny())) + .Returns(new OkResponse>>(handlers)); + } + + private void WhenIBuild() + { + _httpClient = _builder.Create(_context.Items.DownstreamReRoute()); + } + + private void WhenIBuildTheFirstTime() + { + _firstHttpClient = _builder.Create(_context.Items.DownstreamReRoute()); + } + + private void WhenIBuildAgain() + { + _builder = new HttpClientBuilder(_factory.Object, _realCache, _logger.Object); + _againHttpClient = _builder.Create(_context.Items.DownstreamReRoute()); + } + + private void ThenTheHttpClientShouldNotBeNull() + { + _httpClient.ShouldNotBeNull(); + } + + public void Dispose() + { + _response?.Dispose(); + _host?.Dispose(); + } + } +} diff --git a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs index 43bfa5d5..7912e334 100644 --- a/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs +++ b/test/Ocelot.UnitTests/Requester/HttpClientHttpRequesterTest.cs @@ -1,202 +1,198 @@ -using Microsoft.AspNetCore.Http; -using Moq; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Logging; -using Ocelot.Middleware; -using Ocelot.Request.Middleware; -using Ocelot.Requester; -using Ocelot.Responses; -using Shouldly; -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using TestStack.BDDfy; -using Xunit; +using Microsoft.AspNetCore.Http; +using Moq; +using Ocelot.Configuration; +using Ocelot.Configuration.Builder; +using Ocelot.DownstreamRouteFinder.Middleware; +using Ocelot.Logging; +using Ocelot.Middleware; +using Ocelot.Request.Middleware; +using Ocelot.Requester; +using Ocelot.Responses; +using Shouldly; +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Requester +{ + public class HttpClientHttpRequesterTest + { + private readonly Mock _cacheHandlers; + private readonly Mock _factory; + private Response _response; + private readonly HttpClientHttpRequester _httpClientRequester; + private Mock _loggerFactory; + private Mock _logger; + private Mock _mapper; + private HttpContext _httpContext; + + public HttpClientHttpRequesterTest() + { + _httpContext = new DefaultHttpContext(); + _factory = new Mock(); + _factory.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse>>(new List>())); + _logger = new Mock(); + _loggerFactory = new Mock(); + _loggerFactory + .Setup(x => x.CreateLogger()) + .Returns(_logger.Object); + _cacheHandlers = new Mock(); + _mapper = new Mock(); + _httpClientRequester = new HttpClientHttpRequester( + _loggerFactory.Object, + _cacheHandlers.Object, + _factory.Object, + _mapper.Object); + } + + [Fact] + public void should_call_request_correctly() + { + var upstreamTemplate = new UpstreamPathTemplateBuilder().WithOriginalValue("").Build(); + + var qosOptions = new QoSOptionsBuilder() + .Build(); + + var reRoute = new DownstreamReRouteBuilder() + .WithQosOptions(qosOptions) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true, int.MaxValue)) + .WithLoadBalancerKey("") + .WithUpstreamPathTemplate(upstreamTemplate) + .WithQosOptions(new QoSOptionsBuilder().Build()) + .Build(); -namespace Ocelot.UnitTests.Requester -{ - public class HttpClientHttpRequesterTest - { - private readonly Mock _cacheHandlers; - private readonly Mock _factory; - private Response _response; - private readonly HttpClientHttpRequester _httpClientRequester; - private DownstreamContext _request; - private Mock _loggerFactory; - private Mock _logger; - private Mock _mapper; + var httpContext = new DefaultHttpContext(); + httpContext.Items.UpsertDownstreamReRoute(reRoute); + httpContext.Items.UpsertDownstreamRequest(new DownstreamRequest(new HttpRequestMessage() { RequestUri = new Uri("http://www.bbc.co.uk") })); - public HttpClientHttpRequesterTest() - { - _factory = new Mock(); - _factory.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse>>(new List>())); - _logger = new Mock(); - _loggerFactory = new Mock(); - _loggerFactory - .Setup(x => x.CreateLogger()) - .Returns(_logger.Object); - _cacheHandlers = new Mock(); - _mapper = new Mock(); - _httpClientRequester = new HttpClientHttpRequester( - _loggerFactory.Object, - _cacheHandlers.Object, - _factory.Object, - _mapper.Object); - } - - [Fact] - public void should_call_request_correctly() - { - var upstreamTemplate = new UpstreamPathTemplateBuilder().WithOriginalValue("").Build(); - - var qosOptions = new QoSOptionsBuilder() + this.Given(x => x.GivenTheRequestIs(httpContext)) + .And(x => GivenTheHouseReturnsOkHandler()) + .When(x => x.WhenIGetResponse()) + .Then(x => x.ThenTheResponseIsCalledCorrectly()) + .BDDfy(); + } + + [Fact] + public void should_call_request_unable_to_complete_request() + { + var upstreamTemplate = new UpstreamPathTemplateBuilder().WithOriginalValue("").Build(); + + var qosOptions = new QoSOptionsBuilder() + .Build(); + + var reRoute = new DownstreamReRouteBuilder() + .WithQosOptions(qosOptions) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true, int.MaxValue)) + .WithLoadBalancerKey("") + .WithUpstreamPathTemplate(upstreamTemplate) + .WithQosOptions(new QoSOptionsBuilder().Build()) .Build(); - var reRoute = new DownstreamReRouteBuilder() - .WithQosOptions(qosOptions) - .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true, int.MaxValue)) - .WithLoadBalancerKey("") - .WithUpstreamPathTemplate(upstreamTemplate) - .WithQosOptions(new QoSOptionsBuilder().Build()) + var httpContext = new DefaultHttpContext(); + httpContext.Items.UpsertDownstreamReRoute(reRoute); + httpContext.Items.UpsertDownstreamRequest(new DownstreamRequest(new HttpRequestMessage() { RequestUri = new Uri("http://localhost:60080") })); + + this.Given(x => x.GivenTheRequestIs(httpContext)) + .When(x => x.WhenIGetResponse()) + .Then(x => x.ThenTheResponseIsCalledError()) + .BDDfy(); + } + + [Fact] + public void http_client_request_times_out() + { + var upstreamTemplate = new UpstreamPathTemplateBuilder().WithOriginalValue("").Build(); + + var qosOptions = new QoSOptionsBuilder() + .Build(); + + var reRoute = new DownstreamReRouteBuilder() + .WithQosOptions(qosOptions) + .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true, int.MaxValue)) + .WithLoadBalancerKey("") + .WithUpstreamPathTemplate(upstreamTemplate) + .WithQosOptions(new QoSOptionsBuilder().WithTimeoutValue(1).Build()) .Build(); - var context = new DownstreamContext(new DefaultHttpContext()) - { - DownstreamReRoute = reRoute, - DownstreamRequest = new DownstreamRequest(new HttpRequestMessage() { RequestUri = new Uri("http://www.bbc.co.uk") }), - }; - - this.Given(x => x.GivenTheRequestIs(context)) - .And(x => GivenTheHouseReturnsOkHandler()) - .When(x => x.WhenIGetResponse()) - .Then(x => x.ThenTheResponseIsCalledCorrectly()) - .BDDfy(); - } - - [Fact] - public void should_call_request_unable_to_complete_request() - { - var upstreamTemplate = new UpstreamPathTemplateBuilder().WithOriginalValue("").Build(); - - var qosOptions = new QoSOptionsBuilder() - .Build(); - - var reRoute = new DownstreamReRouteBuilder() - .WithQosOptions(qosOptions) - .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true, int.MaxValue)) - .WithLoadBalancerKey("") - .WithUpstreamPathTemplate(upstreamTemplate) - .WithQosOptions(new QoSOptionsBuilder().Build()) - .Build(); - - var context = new DownstreamContext(new DefaultHttpContext()) - { - DownstreamReRoute = reRoute, - DownstreamRequest = new DownstreamRequest(new HttpRequestMessage() { RequestUri = new Uri("http://localhost:60080") }), - }; - - this.Given(x => x.GivenTheRequestIs(context)) - .When(x => x.WhenIGetResponse()) - .Then(x => x.ThenTheResponseIsCalledError()) - .BDDfy(); - } - - [Fact] - public void http_client_request_times_out() - { - var upstreamTemplate = new UpstreamPathTemplateBuilder().WithOriginalValue("").Build(); - - var qosOptions = new QoSOptionsBuilder() - .Build(); - - var reRoute = new DownstreamReRouteBuilder() - .WithQosOptions(qosOptions) - .WithHttpHandlerOptions(new HttpHandlerOptions(false, false, false, true, int.MaxValue)) - .WithLoadBalancerKey("") - .WithUpstreamPathTemplate(upstreamTemplate) - .WithQosOptions(new QoSOptionsBuilder().WithTimeoutValue(1).Build()) - .Build(); - - var context = new DownstreamContext(new DefaultHttpContext()) - { - DownstreamReRoute = reRoute, - DownstreamRequest = new DownstreamRequest(new HttpRequestMessage() { RequestUri = new Uri("http://localhost:60080") }), - }; - - this.Given(_ => GivenTheRequestIs(context)) - .And(_ => GivenTheHouseReturnsTimeoutHandler()) - .When(_ => WhenIGetResponse()) - .Then(_ => ThenTheResponseIsCalledError()) - .And(_ => ThenTheErrorIsTimeout()) - .BDDfy(); - } - - private void GivenTheRequestIs(DownstreamContext request) - { - _request = request; - } - - private void WhenIGetResponse() - { - _response = _httpClientRequester.GetResponse(_request).GetAwaiter().GetResult(); - } - - private void ThenTheResponseIsCalledCorrectly() - { - _response.IsError.ShouldBeFalse(); - } - - private void ThenTheResponseIsCalledError() - { - _response.IsError.ShouldBeTrue(); - } - - private void ThenTheErrorIsTimeout() - { - _mapper.Verify(x => x.Map(It.IsAny()), Times.Once); - _response.Errors[0].ShouldBeOfType(); - } - - private void GivenTheHouseReturnsOkHandler() - { - var handlers = new List> - { - () => new OkDelegatingHandler() - }; - - _factory.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse>>(handlers)); - } - - private void GivenTheHouseReturnsTimeoutHandler() - { - var handlers = new List> - { - () => new TimeoutDelegatingHandler() - }; - - _factory.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse>>(handlers)); - - _mapper.Setup(x => x.Map(It.IsAny())).Returns(new UnableToCompleteRequestError(new Exception())); - } - - private class OkDelegatingHandler : DelegatingHandler - { - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - return Task.FromResult(new HttpResponseMessage()); - } - } - - private class TimeoutDelegatingHandler : DelegatingHandler - { - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - await Task.Delay(100000, cancellationToken); - return new HttpResponseMessage(); - } - } - } -} + var httpContext = new DefaultHttpContext(); + httpContext.Items.UpsertDownstreamReRoute(reRoute); + httpContext.Items.UpsertDownstreamRequest(new DownstreamRequest(new HttpRequestMessage() { RequestUri = new Uri("http://localhost:60080") })); + + this.Given(_ => GivenTheRequestIs(httpContext)) + .And(_ => GivenTheHouseReturnsTimeoutHandler()) + .When(_ => WhenIGetResponse()) + .Then(_ => ThenTheResponseIsCalledError()) + .And(_ => ThenTheErrorIsTimeout()) + .BDDfy(); + } + + private void GivenTheRequestIs(HttpContext httpContext) + { + _httpContext = httpContext; + } + + private void WhenIGetResponse() + { + _response = _httpClientRequester.GetResponse(_httpContext).GetAwaiter().GetResult(); + } + + private void ThenTheResponseIsCalledCorrectly() + { + _response.IsError.ShouldBeFalse(); + } + + private void ThenTheResponseIsCalledError() + { + _response.IsError.ShouldBeTrue(); + } + + private void ThenTheErrorIsTimeout() + { + _mapper.Verify(x => x.Map(It.IsAny()), Times.Once); + _response.Errors[0].ShouldBeOfType(); + } + + private void GivenTheHouseReturnsOkHandler() + { + var handlers = new List> + { + () => new OkDelegatingHandler() + }; + + _factory.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse>>(handlers)); + } + + private void GivenTheHouseReturnsTimeoutHandler() + { + var handlers = new List> + { + () => new TimeoutDelegatingHandler() + }; + + _factory.Setup(x => x.Get(It.IsAny())).Returns(new OkResponse>>(handlers)); + + _mapper.Setup(x => x.Map(It.IsAny())).Returns(new UnableToCompleteRequestError(new Exception())); + } + + private class OkDelegatingHandler : DelegatingHandler + { + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + return Task.FromResult(new HttpResponseMessage()); + } + } + + private class TimeoutDelegatingHandler : DelegatingHandler + { + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + await Task.Delay(100000, cancellationToken); + return new HttpResponseMessage(); + } + } + } +} diff --git a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs index 8a7b1fc8..08242b69 100644 --- a/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Requester/HttpRequesterMiddlewareTests.cs @@ -1,132 +1,132 @@ -namespace Ocelot.UnitTests.Requester -{ - using Microsoft.AspNetCore.Http; - using Moq; - using Ocelot.Configuration.Builder; - using Ocelot.Logging; - using Ocelot.Middleware; - using Ocelot.Requester; - using Ocelot.Requester.Middleware; - using Ocelot.Responses; - using Ocelot.UnitTests.Responder; - using Shouldly; - using System; - using System.Linq; - using System.Net.Http; - using System.Threading.Tasks; - using TestStack.BDDfy; +namespace Ocelot.UnitTests.Requester +{ + using Microsoft.AspNetCore.Http; + using Moq; + using Ocelot.Configuration.Builder; + using Ocelot.Logging; + using Ocelot.Middleware; + using Ocelot.Requester; + using Ocelot.Requester.Middleware; + using Ocelot.Responses; + using Ocelot.UnitTests.Responder; + using Shouldly; + using System; + using System.Linq; + using System.Net.Http; + using System.Threading.Tasks; + using Ocelot.Configuration; + using Ocelot.Infrastructure.RequestData; + using TestStack.BDDfy; using Xunit; + using Ocelot.DownstreamRouteFinder.Middleware; - public class HttpRequesterMiddlewareTests - { - private readonly Mock _requester; - private Response _response; - private Mock _loggerFactory; - private Mock _logger; - private readonly HttpRequesterMiddleware _middleware; - private DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; - - public HttpRequesterMiddlewareTests() + public class HttpRequesterMiddlewareTests + { + private readonly Mock _requester; + private Response _response; + private Mock _loggerFactory; + private Mock _logger; + private readonly HttpRequesterMiddleware _middleware; + private RequestDelegate _next; + private HttpContext _httpContext; + + public HttpRequesterMiddlewareTests() + { + _httpContext = new DefaultHttpContext(); + _requester = new Mock(); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = context => Task.CompletedTask; + _middleware = new HttpRequesterMiddleware(_next, _loggerFactory.Object, _requester.Object); + } + + [Fact] + public void should_call_services_correctly() + { + this.Given(x => x.GivenTheRequestIs()) + .And(x => x.GivenTheRequesterReturns(new OkResponse(new HttpResponseMessage(System.Net.HttpStatusCode.OK)))) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheDownstreamResponseIsSet()) + .Then(x => InformationIsLogged()) + .BDDfy(); + } + + [Fact] + public void should_set_error() + { + this.Given(x => x.GivenTheRequestIs()) + .And(x => x.GivenTheRequesterReturns(new ErrorResponse(new AnyError()))) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheErrorIsSet()) + .BDDfy(); + } + + [Fact] + public void should_log_downstream_internal_server_error() + { + this.Given(x => x.GivenTheRequestIs()) + .And(x => x.GivenTheRequesterReturns( + new OkResponse(new HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError)))) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.WarningIsLogged()) + .BDDfy(); + } + + private void ThenTheErrorIsSet() + { + _httpContext.Items.Errors().Count.ShouldBeGreaterThan(0); + } + + private void WhenICallTheMiddleware() + { + _middleware.Invoke(_httpContext).GetAwaiter().GetResult(); + } + + private void GivenTheRequestIs() { - _requester = new Mock(); - _loggerFactory = new Mock(); - _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = context => Task.CompletedTask; - _middleware = new HttpRequesterMiddleware(_next, _loggerFactory.Object, _requester.Object); - } - - [Fact] - public void should_call_services_correctly() - { - this.Given(x => x.GivenTheRequestIs()) - .And(x => x.GivenTheRequesterReturns(new OkResponse(new HttpResponseMessage(System.Net.HttpStatusCode.OK)))) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheDownstreamResponseIsSet()) - .Then(x => InformationIsLogged()) - .BDDfy(); - } - - [Fact] - public void should_set_error() - { - this.Given(x => x.GivenTheRequestIs()) - .And(x => x.GivenTheRequesterReturns(new ErrorResponse(new AnyError()))) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheErrorIsSet()) - .BDDfy(); - } - - [Fact] - public void should_log_downstream_internal_server_error() - { - this.Given(x => x.GivenTheRequestIs()) - .And(x => x.GivenTheRequesterReturns( - new OkResponse(new HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError)))) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.WarningIsLogged()) - .BDDfy(); - } - - private void ThenTheErrorIsSet() - { - _downstreamContext.IsError.ShouldBeTrue(); - } - - private void WhenICallTheMiddleware() - { - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - } - - private void GivenTheRequestIs() - { - _downstreamContext = - new DownstreamContext(new DefaultHttpContext()) - { - DownstreamReRoute = new DownstreamReRouteBuilder().Build() - }; - } - - private void GivenTheRequesterReturns(Response response) - { - _response = response; - - _requester - .Setup(x => x.GetResponse(It.IsAny())) - .ReturnsAsync(_response); - } - - private void ThenTheDownstreamResponseIsSet() - { - foreach (var httpResponseHeader in _response.Data.Headers) - { - if (_downstreamContext.DownstreamResponse.Headers.Any(x => x.Key == httpResponseHeader.Key)) - { - throw new Exception("Header in response not in downstreamresponse headers"); - } - } - - _downstreamContext.DownstreamResponse.Content.ShouldBe(_response.Data.Content); - _downstreamContext.DownstreamResponse.StatusCode.ShouldBe(_response.Data.StatusCode); - } - - private void WarningIsLogged() - { - _logger.Verify( - x => x.LogWarning( - It.IsAny() - ), - Times.Once); - } - - private void InformationIsLogged() - { - _logger.Verify( - x => x.LogInformation( - It.IsAny() - ), - Times.Once); - } - } -} + _httpContext.Items.UpsertDownstreamReRoute(new DownstreamReRouteBuilder().Build()); + } + + private void GivenTheRequesterReturns(Response response) + { + _response = response; + + _requester + .Setup(x => x.GetResponse(It.IsAny())) + .ReturnsAsync(_response); + } + + private void ThenTheDownstreamResponseIsSet() + { + foreach (var httpResponseHeader in _response.Data.Headers) + { + if (_httpContext.Items.DownstreamResponse().Headers.Any(x => x.Key == httpResponseHeader.Key)) + { + throw new Exception("Header in response not in downstreamresponse headers"); + } + } + + _httpContext.Items.DownstreamResponse().Content.ShouldBe(_response.Data.Content); + _httpContext.Items.DownstreamResponse().StatusCode.ShouldBe(_response.Data.StatusCode); + } + + private void WarningIsLogged() + { + _logger.Verify( + x => x.LogWarning( + It.IsAny() + ), + Times.Once); + } + + private void InformationIsLogged() + { + _logger.Verify( + x => x.LogInformation( + It.IsAny() + ), + Times.Once); + } + } +} diff --git a/test/Ocelot.UnitTests/Responder/AnyError.cs b/test/Ocelot.UnitTests/Responder/AnyError.cs index 9d7b2cd4..a684ce17 100644 --- a/test/Ocelot.UnitTests/Responder/AnyError.cs +++ b/test/Ocelot.UnitTests/Responder/AnyError.cs @@ -1,15 +1,15 @@ -using Ocelot.Errors; - -namespace Ocelot.UnitTests.Responder -{ - internal class AnyError : Error - { - public AnyError() : base("blahh", OcelotErrorCode.UnknownError) - { - } - - public AnyError(OcelotErrorCode errorCode) : base("blah", errorCode) - { - } - } +using Ocelot.Errors; + +namespace Ocelot.UnitTests.Responder +{ + internal class AnyError : Error + { + public AnyError() : base("blahh", OcelotErrorCode.UnknownError, 404) + { + } + + public AnyError(OcelotErrorCode errorCode) : base("blah", errorCode, 404) + { + } + } } diff --git a/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs b/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs index de7ab9d3..0363dfb0 100644 --- a/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Responder/ResponderMiddlewareTests.cs @@ -1,78 +1,80 @@ -using Ocelot.Middleware; - -namespace Ocelot.UnitTests.Responder -{ - using Microsoft.AspNetCore.Http; - using Moq; - using Ocelot.DownstreamRouteFinder.Finder; - using Ocelot.Errors; - using Ocelot.Logging; - using Ocelot.Responder; - using Ocelot.Responder.Middleware; - using System.Net.Http; - using System.Threading.Tasks; - using TestStack.BDDfy; - using Xunit; - - public class ResponderMiddlewareTests - { - private readonly Mock _responder; - private readonly Mock _codeMapper; - private Mock _loggerFactory; - private Mock _logger; - private readonly ResponderMiddleware _middleware; - private readonly DownstreamContext _downstreamContext; - private OcelotRequestDelegate _next; - - public ResponderMiddlewareTests() - { - _responder = new Mock(); - _codeMapper = new Mock(); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - _loggerFactory = new Mock(); - _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _next = context => Task.CompletedTask; - _middleware = new ResponderMiddleware(_next, _responder.Object, _loggerFactory.Object, _codeMapper.Object); - } - - [Fact] - public void should_not_return_any_errors() - { - this.Given(x => x.GivenTheHttpResponseMessageIs(new DownstreamResponse(new HttpResponseMessage()))) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenThereAreNoErrors()) - .BDDfy(); - } - - [Fact] - public void should_return_any_errors() - { - this.Given(x => x.GivenTheHttpResponseMessageIs(new DownstreamResponse(new HttpResponseMessage()))) - .And(x => x.GivenThereArePipelineErrors(new UnableToFindDownstreamRouteError("/path", "GET"))) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenThereAreNoErrors()) - .BDDfy(); - } - - private void WhenICallTheMiddleware() - { - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - } - - private void GivenTheHttpResponseMessageIs(DownstreamResponse response) - { - _downstreamContext.DownstreamResponse = response; - } - - private void ThenThereAreNoErrors() - { - //todo a better assert? - } - - private void GivenThereArePipelineErrors(Error error) - { - _downstreamContext.Errors.Add(error); +using Ocelot.Middleware; + +namespace Ocelot.UnitTests.Responder +{ + using Microsoft.AspNetCore.Http; + using Moq; + using Ocelot.DownstreamRouteFinder.Finder; + using Ocelot.Errors; + using Ocelot.Logging; + using Ocelot.Responder; + using Ocelot.Responder.Middleware; + using System.Net.Http; + using System.Threading.Tasks; + using Ocelot.Infrastructure.RequestData; + using TestStack.BDDfy; + using Xunit; + using Ocelot.DownstreamRouteFinder.Middleware; + + public class ResponderMiddlewareTests + { + private readonly Mock _responder; + private readonly Mock _codeMapper; + private Mock _loggerFactory; + private Mock _logger; + private readonly ResponderMiddleware _middleware; + private RequestDelegate _next; + private HttpContext _httpContext; + + public ResponderMiddlewareTests() + { + _httpContext = new DefaultHttpContext(); + _responder = new Mock(); + _codeMapper = new Mock(); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _next = context => Task.CompletedTask; + _middleware = new ResponderMiddleware(_next, _responder.Object, _loggerFactory.Object, _codeMapper.Object); } - } + + [Fact] + public void should_not_return_any_errors() + { + this.Given(x => x.GivenTheHttpResponseMessageIs(new DownstreamResponse(new HttpResponseMessage()))) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenThereAreNoErrors()) + .BDDfy(); + } + + [Fact] + public void should_return_any_errors() + { + this.Given(x => x.GivenTheHttpResponseMessageIs(new DownstreamResponse(new HttpResponseMessage()))) + .And(x => x.GivenThereArePipelineErrors(new UnableToFindDownstreamRouteError("/path", "GET"))) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenThereAreNoErrors()) + .BDDfy(); + } + + private void WhenICallTheMiddleware() + { + _middleware.Invoke(_httpContext).GetAwaiter().GetResult(); + } + + private void GivenTheHttpResponseMessageIs(DownstreamResponse response) + { + _httpContext.Items.UpsertDownstreamResponse(response); + } + + private void ThenThereAreNoErrors() + { + //todo a better assert? + } + + private void GivenThereArePipelineErrors(Error error) + { + _httpContext.Items.SetError(error); + } + } } diff --git a/test/Ocelot.UnitTests/Security/IPSecurityPolicyTests.cs b/test/Ocelot.UnitTests/Security/IPSecurityPolicyTests.cs index 637048f5..21f4f33a 100644 --- a/test/Ocelot.UnitTests/Security/IPSecurityPolicyTests.cs +++ b/test/Ocelot.UnitTests/Security/IPSecurityPolicyTests.cs @@ -1,36 +1,37 @@ -using Microsoft.AspNetCore.Http; -using Ocelot.Configuration; -using Ocelot.Configuration.Builder; -using Ocelot.Middleware; -using Ocelot.Request.Middleware; -using Ocelot.Responses; -using Ocelot.Security.IPSecurity; -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Security +namespace Ocelot.UnitTests.Security { + using Microsoft.AspNetCore.Http; + using Ocelot.Configuration; + using Ocelot.Configuration.Builder; + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.Middleware; + using Ocelot.Request.Middleware; + using Ocelot.Responses; + using Ocelot.Security.IPSecurity; + using System.Collections.Generic; + using System.Net; + using System.Net.Http; + using TestStack.BDDfy; + using Xunit; + public class IPSecurityPolicyTests { - private readonly DownstreamContext _downstreamContext; private readonly DownstreamReRouteBuilder _downstreamReRouteBuilder; private readonly IPSecurityPolicy _ipSecurityPolicy; private Response response; + private HttpContext _httpContext; public IPSecurityPolicyTests() { - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - _downstreamContext.DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); - _downstreamContext.HttpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.1")[0]; + _httpContext = new DefaultHttpContext(); + _httpContext.Items.UpsertDownstreamRequest(new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com"))); + _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.1")[0]; _downstreamReRouteBuilder = new DownstreamReRouteBuilder(); _ipSecurityPolicy = new IPSecurityPolicy(); } [Fact] - private void should_No_blocked_Ip_and_allowed_Ip() + public void should_No_blocked_Ip_and_allowed_Ip() { this.Given(x => x.GivenSetDownstreamReRoute()) .When(x => x.WhenTheSecurityPolicy()) @@ -39,9 +40,9 @@ namespace Ocelot.UnitTests.Security } [Fact] - private void should_blockedIp_clientIp_block() + public void should_blockedIp_clientIp_block() { - _downstreamContext.HttpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.1")[0]; + _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.1")[0]; this.Given(x => x.GivenSetBlockedIP()) .Given(x => x.GivenSetDownstreamReRoute()) .When(x => x.WhenTheSecurityPolicy()) @@ -50,9 +51,9 @@ namespace Ocelot.UnitTests.Security } [Fact] - private void should_blockedIp_clientIp_Not_block() + public void should_blockedIp_clientIp_Not_block() { - _downstreamContext.HttpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.2")[0]; + _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.2")[0]; this.Given(x => x.GivenSetBlockedIP()) .Given(x => x.GivenSetDownstreamReRoute()) .When(x => x.WhenTheSecurityPolicy()) @@ -61,9 +62,9 @@ namespace Ocelot.UnitTests.Security } [Fact] - private void should_allowedIp_clientIp_block() + public void should_allowedIp_clientIp_block() { - _downstreamContext.HttpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.1")[0]; + _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.1")[0]; this.Given(x => x.GivenSetAllowedIP()) .Given(x => x.GivenSetDownstreamReRoute()) .When(x => x.WhenTheSecurityPolicy()) @@ -72,9 +73,9 @@ namespace Ocelot.UnitTests.Security } [Fact] - private void should_allowedIp_clientIp_Not_block() + public void should_allowedIp_clientIp_Not_block() { - _downstreamContext.HttpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.2")[0]; + _httpContext.Connection.RemoteIpAddress = Dns.GetHostAddresses("192.168.1.2")[0]; this.Given(x => x.GivenSetAllowedIP()) .Given(x => x.GivenSetDownstreamReRoute()) .When(x => x.WhenTheSecurityPolicy()) @@ -94,12 +95,12 @@ namespace Ocelot.UnitTests.Security private void GivenSetDownstreamReRoute() { - _downstreamContext.DownstreamReRoute = _downstreamReRouteBuilder.Build(); + _httpContext.Items.UpsertDownstreamReRoute(_downstreamReRouteBuilder.Build()); } private void WhenTheSecurityPolicy() { - response = this._ipSecurityPolicy.Security(_downstreamContext).GetAwaiter().GetResult(); + response = _ipSecurityPolicy.Security(_httpContext.Items.DownstreamReRoute(), _httpContext).GetAwaiter().GetResult(); } private void ThenSecurityPassing() diff --git a/test/Ocelot.UnitTests/Security/SecurityMiddlewareTests.cs b/test/Ocelot.UnitTests/Security/SecurityMiddlewareTests.cs index 7d2b3d89..b329fbc4 100644 --- a/test/Ocelot.UnitTests/Security/SecurityMiddlewareTests.cs +++ b/test/Ocelot.UnitTests/Security/SecurityMiddlewareTests.cs @@ -1,106 +1,110 @@ -using Microsoft.AspNetCore.Http; -using Moq; -using Ocelot.Errors; -using Ocelot.Logging; -using Ocelot.Middleware; -using Ocelot.Request.Middleware; -using Ocelot.Responses; -using Ocelot.Security; -using Ocelot.Security.Middleware; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using TestStack.BDDfy; -using Xunit; - -namespace Ocelot.UnitTests.Security +using Microsoft.AspNetCore.Http; +using Moq; +using Ocelot.Errors; +using Ocelot.Logging; +using Ocelot.Middleware; +using Ocelot.Request.Middleware; +using Ocelot.Responses; +using Ocelot.Security; +using Ocelot.Security.Middleware; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using TestStack.BDDfy; +using Xunit; + +namespace Ocelot.UnitTests.Security { - public class SecurityMiddlewareTests - { - private List> _securityPolicyList; - private Mock _loggerFactory; - private Mock _logger; - private readonly SecurityMiddleware _middleware; - private readonly DownstreamContext _downstreamContext; - private readonly OcelotRequestDelegate _next; + using Ocelot.DownstreamRouteFinder.Middleware; + using Ocelot.Infrastructure.RequestData; + using Shouldly; - public SecurityMiddlewareTests() + public class SecurityMiddlewareTests + { + private List> _securityPolicyList; + private Mock _loggerFactory; + private Mock _logger; + private readonly SecurityMiddleware _middleware; + private readonly RequestDelegate _next; + private HttpContext _httpContext; + + public SecurityMiddlewareTests() + { + _httpContext = new DefaultHttpContext(); + _loggerFactory = new Mock(); + _logger = new Mock(); + _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); + _securityPolicyList = new List>(); + _securityPolicyList.Add(new Mock()); + _securityPolicyList.Add(new Mock()); + _next = context => + { + return Task.CompletedTask; + }; + _middleware = new SecurityMiddleware(_next, _loggerFactory.Object, _securityPolicyList.Select(f => f.Object).ToList()); + _httpContext.Items.UpsertDownstreamRequest(new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com"))); + } + + [Fact] + public void should_legal_request() + { + this.Given(x => x.GivenPassingSecurityVerification()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheRequestIsPassingSecurity()) + .BDDfy(); + } + + [Fact] + public void should_verification_failed_request() + { + this.Given(x => x.GivenNotPassingSecurityVerification()) + .When(x => x.WhenICallTheMiddleware()) + .Then(x => x.ThenTheRequestIsNotPassingSecurity()) + .BDDfy(); + } + + private void GivenPassingSecurityVerification() + { + foreach (var item in _securityPolicyList) + { + Response response = new OkResponse(); + item.Setup(x => x.Security(_httpContext.Items.DownstreamReRoute(), _httpContext)).Returns(Task.FromResult(response)); + } + } + + private void GivenNotPassingSecurityVerification() + { + for (int i = 0; i < _securityPolicyList.Count; i++) + { + Mock item = _securityPolicyList[i]; + if (i == 0) + { + Error error = new UnauthenticatedError($"Not passing security verification"); + Response response = new ErrorResponse(error); + item.Setup(x => x.Security(_httpContext.Items.DownstreamReRoute(), _httpContext)).Returns(Task.FromResult(response)); + } + else + { + Response response = new OkResponse(); + item.Setup(x => x.Security(_httpContext.Items.DownstreamReRoute(), _httpContext)).Returns(Task.FromResult(response)); + } + } + } + + private void WhenICallTheMiddleware() + { + _middleware.Invoke(_httpContext).GetAwaiter().GetResult(); + } + + private void ThenTheRequestIsPassingSecurity() { - _loggerFactory = new Mock(); - _logger = new Mock(); - _loggerFactory.Setup(x => x.CreateLogger()).Returns(_logger.Object); - _securityPolicyList = new List>(); - _securityPolicyList.Add(new Mock()); - _securityPolicyList.Add(new Mock()); - _next = context => - { - return Task.CompletedTask; - }; - _middleware = new SecurityMiddleware(_loggerFactory.Object, _securityPolicyList.Select(f => f.Object).ToList(), _next); - _downstreamContext = new DownstreamContext(new DefaultHttpContext()); - _downstreamContext.DownstreamRequest = new DownstreamRequest(new HttpRequestMessage(HttpMethod.Get, "http://test.com")); - } - - [Fact] - public void should_legal_request() + _httpContext.Items.Errors().Count.ShouldBe(0); + } + + private void ThenTheRequestIsNotPassingSecurity() { - this.Given(x => x.GivenPassingSecurityVerification()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheRequestIsPassingSecurity()) - .BDDfy(); - } - - [Fact] - public void should_verification_failed_request() - { - this.Given(x => x.GivenNotPassingSecurityVerification()) - .When(x => x.WhenICallTheMiddleware()) - .Then(x => x.ThenTheRequestIsNotPassingSecurity()) - .BDDfy(); - } - - private void GivenPassingSecurityVerification() - { - foreach (var item in _securityPolicyList) - { - Response response = new OkResponse(); - item.Setup(x => x.Security(_downstreamContext)).Returns(Task.FromResult(response)); - } - } - - private void GivenNotPassingSecurityVerification() - { - for (int i = 0; i < _securityPolicyList.Count; i++) - { - Mock item = _securityPolicyList[i]; - if (i == 0) - { - Error error = new UnauthenticatedError($"Not passing security verification"); - Response response = new ErrorResponse(error); - item.Setup(x => x.Security(_downstreamContext)).Returns(Task.FromResult(response)); - } - else - { - Response response = new OkResponse(); - item.Setup(x => x.Security(_downstreamContext)).Returns(Task.FromResult(response)); - } - } - } - - private void WhenICallTheMiddleware() - { - _middleware.Invoke(_downstreamContext).GetAwaiter().GetResult(); - } - - private void ThenTheRequestIsPassingSecurity() - { - Assert.False(_downstreamContext.IsError); - } - - private void ThenTheRequestIsNotPassingSecurity() - { - Assert.True(_downstreamContext.IsError); - } - } -} + _httpContext.Items.Errors().Count.ShouldBeGreaterThan(0); + } + } +}