Skip to content

Latest commit

 

History

History
470 lines (331 loc) · 20.6 KB

routing.rst

File metadata and controls

470 lines (331 loc) · 20.6 KB

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 describes the routing of one request to another as a Route. In order to get anything working in Ocelot you need to set up a Route in the configuration.

{
  "Routes": []
}

To configure a Route you need to add one to the Routes JSON array.

{
  "UpstreamHttpMethod": [ "Put", "Delete" ],
  "UpstreamPathTemplate": "/posts/{postId}",
  "DownstreamPathTemplate": "/api/posts/{postId}",
  "DownstreamScheme": "https",
  "DownstreamHostAndPorts": [
    { "Host": "localhost", "Port": 80 }
  ]
}

The DownstreamPathTemplate, DownstreamScheme and DownstreamHostAndPorts define the URL that a request will be forwarded to.

The DownstreamHostAndPorts property 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 property 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.

Placeholders

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 :ref:`routing-catch-all` type of Route e.g.

{
  "UpstreamHttpMethod": [ "Get", "Post" ],
  "UpstreamPathTemplate": "/{everything}",
  "DownstreamPathTemplate": "/api/{everything}",
  "DownstreamScheme": "https",
  "DownstreamHostAndPorts": [
    { "Host": "localhost", "Port": 80 }
  ]
}

This will forward any path + query string combinations to the downstream service after the path /api.

Note, the default Routing configuration is case insensitive!

In order to change this you can specify on a per Route basis the following setting:

"RouteIsCaseSensitive": true

This means that when Ocelot tries to match the incoming upstream URL with an upstream template the evaluation will be case sensitive.

Embedded Placeholders [1]

Prior to version 23.4, Ocelot was unable to evaluate multiple placeholders embedded between two forward slashes, /. It was also challenging to differentiate the placeholder from other elements within the slashes. For example, /{url}-2/ when applied to /y-2/ would yield {url} = y-2.

We now introduce an improved method of placeholder evaluation that facilitates the identification of placeholders in complex URLs. For example:

  • Given path pattern: /api/invoices_{url0}/{url1}-{url2}_abcd/{url3}?urlId={url4}

  • When upstream URL path: /api/invoices_super/123-456_abcd/789?urlId=987

  • Then resulting placeholders would be {url0} = super, {url1} = 123, {url2} = 456, {url3} = 789, {url4} = 987

    Note, we believe this feature should be compatible with any URL query strings, although it has not been thoroughly tested.

Empty Placeholders [2]

This is a special edge case of :ref:`routing-placeholders`, where the value of the placeholder is simply an empty string "".

For example, Given a route:

{
  "UpstreamPathTemplate": "/invoices/{url}",
  "DownstreamPathTemplate": "/api/invoices/{url}",
}
Then, it works correctly when {url} is specified: /invoices/123 /api/invoices/123.
And then, there are two edge cases with empty placeholder value:
  • Also, it works when {url} is empty. We would expect upstream path /invoices/ to route to downstream path /api/invoices/
  • Moreover, it should work when omitting last slash. We also expect upstream /invoices to be routed to downstream /api/invoices, which is intuitive to humans

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.

{
  "UpstreamHttpMethod": [ "Get" ],
  "UpstreamPathTemplate": "/{url}",
  "DownstreamPathTemplate": "/{url}",
  "DownstreamScheme": "https",
  "DownstreamHostAndPorts": [
    { "Host": "localhost", "Port": 80 }
  ]
}

The Catch All has a lower priority than any other Route. If you also have the Route below in your config then Ocelot would match it before the Catch All.

{
  "UpstreamHttpMethod": [ "Get" ],
  "UpstreamPathTemplate": "/",
  "DownstreamPathTemplate": "/",
  "DownstreamScheme": "https",
  "DownstreamHostAndPorts": [
    { "Host": "10.0.10.1", "Port": 80 }
  ]
}

Upstream Host [3]

This feature allows you to have Routes 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 Route.

In order to use this feature please add the following to your config:

{
  "UpstreamHost": "somedomain.com"
}

The Route above will only be matched when the Host header value is somedomain.com.

If you do not set UpstreamHost on a Route then any Host header will match it. This means that if you have two Routes 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.

Upstream Headers [4]

In addition to routing by UpstreamPathTemplate, you can also define UpstreamHeaderTemplates. For a route to match, all headers specified in this dictionary object must be present in the request headers.

{
  // ...
  "UpstreamPathTemplate": "/",
  "UpstreamHttpMethod": [ "Get" ],
  "UpstreamHeaderTemplates": { // dictionary
    "country": "uk", // 1st header
    "version": "v1"  // 2nd header
  }
}

In this scenario, the route will only match if a request includes both headers with the specified values.

Header placeholders

Let's explore a more intriguing scenario where placeholders can be effectively utilized within your UpstreamHeaderTemplates.

Consider the following approach using the special placeholder format {header:placeholdername}:

{
  "DownstreamPathTemplate": "/{versionnumber}/api", // with placeholder
  "DownstreamScheme": "https",
  "DownstreamHostAndPorts": [
    { "Host": "10.0.10.1", "Port": 80 }
  ],
  "UpstreamPathTemplate": "/api",
  "UpstreamHttpMethod": [ "Get" ],
  "UpstreamHeaderTemplates": {
    "version": "{header:versionnumber}" // 'header:' prefix vs placeholder
  }
}

In this scenario, the entire value of the request header "version" is inserted into the DownstreamPathTemplate. If necessary, a more intricate upstream header template can be specified, using placeholders such as version-{header:version}_country-{header:country}.

Note 1: Placeholders are not required in DownstreamPathTemplate. This scenario can be utilized to mandate a specific header regardless of its value.

Note 2: Additionally, the UpstreamHeaderTemplates dictionary options are applicable for :doc:`../features/requestaggregation` as well.

Priority

You can define the order you want your Routes to match the Upstream HttpRequest by including a Priority property in ocelot.json. See issue 270 for reference.

{
  "Priority": 0
}

0 is the lowest priority, Ocelot will always use 0 for /{catchAll} Routes and this is still hardcoded. After that you are free to set any priority you wish.

e.g. you could have

{
  "UpstreamPathTemplate": "/goods/{catchAll}",
  "Priority": 0
}

and

{
  "UpstreamPathTemplate": "/goods/delete",
  "Priority": 1
}

In the example above if you make a request into Ocelot on /goods/delete, Ocelot will match /goods/delete Route. Previously it would have matched /goods/{catchAll}, because this is the first Route in the list!

Query String Placeholders

In addition to URL path :ref:`routing-placeholders` Ocelot is able to forward query string parameters with their processing in the form of {something}. Also, the query parameter placeholder needs to be present in both the DownstreamPathTemplate and UpstreamPathTemplate properties. Placeholder replacement works bi-directionally between path and query strings, with some restrictions on usage (see :ref:`routing-merging-of-query-parameters`).

Path to Query String direction

Ocelot allows you to specify a query string as part of the DownstreamPathTemplate like the example below:

{
  "UpstreamPathTemplate": "/api/units/{subscription}/{unit}/updates",
  "DownstreamPathTemplate": "/api/subscriptions/{subscription}/updates?unitId={unit}",
}

In this example Ocelot will use the value from the {unit} placeholder in the upstream path template and add it to the downstream request as a query string parameter called unitId!

Note! Make sure you name the placeholder differently due to :ref:`routing-merging-of-query-parameters`.

Query String to Path direction

Ocelot will also allow you to put query string parameters in the UpstreamPathTemplate so you can match certain queries to certain services:

{
  "UpstreamPathTemplate": "/api/subscriptions/{subscriptionId}/updates?unitId={uid}",
  "DownstreamPathTemplate": "/api/units/{subscriptionId}/{uid}/updates",
}

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 {uid} parameter from the query string and use it in the downstream request path.

Note, the best practice is giving different placeholder name than the name of query parameter due to :ref:`routing-merging-of-query-parameters`.

Catch All Query String

Ocelot's routing also supports a :ref:`routing-catch-all` style routing to forward all query string parameters. The placeholder {everything} name does not matter, any name will work.

{
  "UpstreamPathTemplate": "/contracts?{everything}",
  "DownstreamPathTemplate": "/apipath/contracts?{everything}",
}

This entire query string routing feature is very useful in cases where the query string should not be transformed but rather routed without any changes, such as OData filters and etc (see issue 1174).

Note, the {everything} placeholder can be empty while catching all query strings, because this is a part of the :ref:`routing-empty-placeholders` feature! [2] Thus, upstream paths /contracts? and /contracts are routed to downstream path /apipath/contracts, which has no query string at all.

Merging of Query Parameters

Query string parameters are unsorted and merged to create the final downstream URL. This process is essential as the DownstreamUrlCreatorMiddleware requires control over placeholder replacement and the merging of duplicate parameters. A parameter that appears first in the UpstreamPathTemplate may occupy a different position in the final downstream URL. Furthermore, if the DownstreamPathTemplate includes query parameters at the beginning, their position in the UpstreamPathTemplate will be indeterminate unless explicitly defined.

In a typical scenario, the merging algorithm constructs the final downstream URL query string by:

  1. Taking the initially defined query parameters in DownstreamPathTemplate and placing them at the beginning, with any necessary placeholder replacements.
  2. Adding all parameters from the :ref:`routing-catch-all-query-string`, represented by the placeholder {everything}, into the second position (following the explicitly defined parameters from step 1).
  3. Appending any remaining replaced placeholder values as parameter values to the end of the string, if they are present.

Array parameters in ASP.NET API's model binding

Due to parameters merging, ASP.NET API's special model binding for arrays is not supported having the array item representation format of selectedCourses=1050&selectedCourses=2000. This query string will be merged into selectedCourses=1050 in the downstream URL, resulting in the loss of array data. It is crucial for upstream clients to generate the correct query string for array models, such as selectedCourses[0]=1050&selectedCourses[1]=2000. For a comprehensive understanding of array model bindings, refer to the documentation: Bind arrays and string values from headers and query strings.

Control over parameter existence

Be aware that query string placeholders are subject to naming restrictions due to the DownstreamUrlCreatorMiddleware's merging algorithm implementation. However, this also provides the flexibility to manage the presence of parameters in the final downstream URL by their names.

Consider the following 2 development scenarios

  1. A developer wishes to preserve a parameter after substituting a placeholder (refer to issue 473). This requires the use of the template definition below:

    {
      "UpstreamPathTemplate": "/path/{serverId}/{action}",
      "DownstreamPathTemplate": "/path2/{action}?server={serverId}"
    }
    Here, the {serverId} placeholder and the server parameter names differ! Ultimately, the server parameter is retained.
    It is important to note that due to the case-sensitive comparison of names, the server parameter will not be preserved with the {server} placeholder. However, using the {Server} placeholder is acceptable for retaining the parameter.
  2. The developer intends to remove an outdated parameter after substituting a placeholder (refer to issue 952). For this action, you must use identical names having the case-sensitive comparison:

    {
      "UpstreamPathTemplate": "/users?userId={userId}",
      "DownstreamPathTemplate": "/persons?personId={userId}"
    }
    Thus, the {userId} placeholder and the userId parameter have identical names! Subsequently, the userId parameter is eliminated.
    Be aware that due to the case sensitive nature of the comparison, if the {userid} placeholder is used, the userId parameter will not be removed!

Security Options [5]

Ocelot enables the management of multiple patterns for allowed and blocked IPs using the IPAddressRange package, which is licensed under the MPL-2.0 license.

This feature is designed to enhanced IP management, allowing for the inclusion exclusion of a broad IP range through CIDR notation or specific IP ranges. The current managed patterns are as follows:

IP Rule Example
Single IP 192.168.1.1
IP Range 192.168.1.1-192.168.1.250
IP Short Range 192.168.1.1-250
IP Subnet 192.168.1.0/255.255.255.0
CIDR IPv4 192.168.1.0/24
CIDR IPv6 fe80::/10

Here is a quick example:

{
  "SecurityOptions": {
    "IPBlockedList": [ "192.168.0.0/23" ],
    "IPAllowedList": ["192.168.0.15", "192.168.1.15"],
    "ExcludeAllowedFromBlocked": true
  }
}

Please note that:

  • The allowed/blocked lists are evaluated during configuration loading
  • The ExcludeAllowedFromBlocked property is intended to provide the ability to specify a wide range of blocked IP addresses and allow a subrange of IP addresses. Default value: false
  • The absence of a property in Security Options is allowed, it takes the default value.
  • The Security Options can be configured globally in the GlobalConfiguration JSON; however, it is ignored if overriding options are specified at the route level [6].

Dynamic Routing [7]

The idea is to enable dynamic routing when using a :doc:`../features/servicediscovery` provider so you don't have to provide the Route config. See the :ref:`sd-dynamic-routing` docs if this sounds interesting to you.

[1]":ref:`routing-embedded-placeholders`" feature was requested as part of issue 2199 , and released in version 23.4
[2](1, 2) ":ref:`routing-empty-placeholders`" feature is available starting in version 23.0, see issue 748 and the 23.0 release notes for details.
[3]":ref:`routing-upstream-host`" feature was requested as part of issue 216.
[4]":ref:`routing-upstream-headers`" feature was proposed in issue 360, and released in version 23.3.
[5]":ref:`routing-security-options`" feature was requested as part of issue 628 (version 12.0.1), then redesigned and improved by issue 1400, and published in version 20.0 docs.
[6]Global ":ref:`routing-security-options`" feature was requested as part of issue 2165 , and released in version 23.4.1.
[7]":ref:`routing-dynamic`" feature was requested as part of issue 340. Refer to complete reference: :ref:`sd-dynamic-routing`.