Dispatcher
Introduction
The Dispatcher service is an API Gateway providing a way to route APIs. It is built based on the Spring Cloud Gateway and includes all its features (as describe in the Spring Cloud Gateway public documentation). It also provides path rewriting rules for Yambas and the ApiOmat cloud environment. See How It works to have a deeper understanding of how the Spring Cloud Gateway works.
Installation
Usage
Dispatcher loads all rulesets at service start. Therefore Consul is queried for all existing services and the contextPath of each service is used to build up a rule for redirection to that service if the context path matches. If new services are registered at Consul, the ruleset has to be refreshed for Dispatcher to become aware of new services. More information: Actuator API
Getting existing Routes
To retrieve the current routes which are mapped by dispatcher use the following REST endpoint:bash
curl http:
//
{DISPATCHER_HOST}
/actuator/gateway/routes
The received response object is a list of defined routes which looks like this:bash
[
{
"route_id"
:
"{ROUTE_ID}'"
,
"route_definition"
: {
"id"
:
"{ROUTE_DEFINITION_ID}"
,
"predicates"
: [
{
"name"
:
"{PREDICATE_NAME}"
,
"args"
: {
"{KEY_1}"
:
"{VALUE_1}"
}
}
],
"filters"
: [
{
"name"
:
"{FILTER_NAME}"
,
"args"
: {
"{KEY_1}"
:
"{VALUE_1}"
}
}
],
"uri"
:
"{URI_NAME}"
,
"order"
: 1
},
"order"
: 1
},
[...]
]
The response contains the following elements:
-
Route: The basic building block of the gateway. It is defined by an id (ROUTE_ID), a destination uri (URI_NAME), a collection of predicates (PREDICATE_NAME), and a collection of filters (FILTER_NAME). A route is matched if the aggregate predicate is true.
-
Predicate: This is a Java 8 Function Predicate. The input type is a Spring Framework ServerWebExchange. This lets you match on anything from the HTTP request, such as headers or parameters.
-
Filter: These are instances of Spring Framework GatewayFilter that have been constructed with a specific factory. Here, you can modify requests and responses before or after sending the downstream request.
You can find further information in the Examples section below about configuring route definitions with predicates and with filters.
Manually reloading Routes
If it happens that the dispatcher doesn't automatically refreshes its routes, you can also use the following curl request to trigger the refreshing manually:bash
curl -X POST http:
//
{DISPATCHER_HOST}
/actuator/gateway/refresh
Service Registration Options
Other services can modify their behavior on how to register to Dispatcher. Use the following tags in the discovery configuration of your service (e.g. for Spring Boot applications: spring.cloud.consul.dicovery.tags in application.yaml):
-
registerAsYambasContextPath: Add this tag to create routes for the microservice's REST interface paths that match the YAMBAS REST path conventions. This option is especially used for generated microservices to make them transparent to existing SDKs.
-
notRegisterOnGateway: Add this tag to hide the microservice from outside which means it will not be exposed through the gateway and will be only accessible behind the Dispatcher.
Configure CORS Handling
Since Dispatcher version 1.0.1 the following configuration for Cross-Origin Resource Sharing exists as default within the application.yml:
spring:
cloud:
gateway:
default-filters:
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins: "*"
allowedHeaders: "*"
allowedMethods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
discovery:
locator:
enabled: false
This means that by default:
-
CORS requests are allowed from requests that originate from any domain for all paths and all headers
-
Duplicate response header values of Access-Control-Allow-Credentials and Access-Control-Allow-Origin will be removed in cases when both the gateway CORS logic and the downstream logic add them.
The default strategy is set to RETAIN_FIRST (default) which means that the first header value which is found will be taken.
-
Routes are not created based on services registered by the default DiscoveryClient implementation (spring.cloud.gateway.discovery.locator.enabled: false).
Secure your routes with Spring Security and OAuth2
By using OAuth 2.0 protocol, users can grant limited access to their resources on one site to another site without having to share or expose their credentials. In the Dispatcher service we are using OAuth 2.0 protocol and Spring Security to provide the possibility to secure some parts of your applications by forcing authentication.
Application configuration
A client configuration is needed for the application to provide these mechanism. OAuth 2.0 client registration and provider can be configured for Dispatcher through the following properties (In the following configuration, our authentication provider Service which is called Bouncer is used and a client is created in Bouncer. Any other authentication providers that supports OAuth 2.0 can be used as well):
Property Key |
Description |
Example value |
security.oauth2.client.registration.Easy.client-id |
Client ID used for identification and configured in the authentication provider. |
Dispatcher |
security.oauth2.client.registration.Easy.client-secret |
Client secret credentials provided by your authentication provider. |
123456-1a2b-1a2b-3c4d-123abc456 |
security.oauth2.client.registration.Easy.provider |
Authentication provider used by this client application. |
bouncer |
security.oauth2.client.registration.Easy.redirect-uri |
Redirect URI for authentication. |
{baseUrl}/login/oauth2/code/{registrationId} |
security.oauth2.client.registration.Easy.authorization-grant-type |
Authorization grant type. |
authorization_code |
security.oauth2.client.registration.Easy.client-authentication-method |
Client authentication method. |
post |
security.oauth2.client.registration.Easy.scope |
Scopes of your client. Defaults may depend on your authentication provider settings. For example; sending openid as a scope is required to retrieve attributes of user from userinfo endpoint. |
openid, address |
security.oauth2.client.provider.bouncer.token-uri |
URI from which the client application can retrieve a token using its client id and client secret. |
http://localhost:8095/auth/realms/Easy/protocol/openid-connect/auth |
security.oauth2.client.provider.bouncer.authorization-uri |
URI from which the client application can assert the validity of a token. |
http://localhost:8095/auth/realms/Easy/protocol/openid-connect/auth |
security.oauth2.client.provider.bouncer.issuer-uri |
URI that provide all information about token and authorisation, making toker-uri and authorization-uri facultative. |
http://localhost:8095/auth/realms/Easy |
To get started, you should create a client in your preferred Authentication Provider. In above example our Authentication Provider Service which is called Bouncer is used. Change client-id, client-secret, authorization-uri and token-uri with your client and provider settings. Other configurations can be kept as default.
Where to get these configuration values
Our authentication provider service is Bouncer. In these example we consider that it is started on localhost port 8095.
Client Configuration
You should create a client in your authentication provider then you will have client-id and client-secret. In the above example the client is created with authorization_code flow of OAuth 2.0.
Provider Configuration
You can statically write your provider settings for token-uri, authorization-uri and user-info-uri of your authentication provider.
Key |
Value |
token-uri |
http://localhost:8095/auth/realms/Easy/protocol/openid-connect/token |
authorization-uri |
http://localhost:8095/auth/realms/Easy/protocol/openid-connect/auth |
user-info-uri |
http://localhost:8095/auth/realms/Easy/protocol/openid-connect/userinfo |
If your provider supports /.well-known/openid-configuration endpoint which is OAuth 2.0 standard, then you can set only issuer-uri to fetch your provider settings, Dispatcher will then retrieve them dynamically on startup. Because Bouncer is OAuth2 compliant, below is OpenID Connection configuration URL to get details about all security endpoints considering that we configured a realm named Easy and Bouncer is running on localhost port 8095, than configuring issuer-uri is enough:
Key |
Value |
issuer-uri |
http://localhost:8095/auth/realms/Easy/protocol/openid-connect/token |
Configure the secured paths
By default Dispatcher does not require authentication for any application. To force authentication for specific application registered to Dispatcher you can use the following property configuration (e.g. securing all endpoints to /example and /myapp):
dispatcher:
security:
route-matchers:
- /example/**
- /myapp/**
Note that you can't set security configuration at runtime.
Use a filter to relay the token within the proxied call
If you are using OAuth2 to generate token for a specific application at Dispatcher, you will have to use a token relay filter in the route definition to convey it to your secured application. This can be achieved with the following definition, e.g. for an application SecuredBackend running on localhost port 9999 with a context path /securedApp :
routes:
- id: securedApp
uri: http://localhost:9999
predicates:
- Path=/securedApp/**
filters:
- TokenRelay=
Examples
This example section helps you to overcome the first barriers when using Spring Cloud Gateway functionalites. In Usage section above you already read about routes, predicates and filters. This section helps you to actually set them up.
Route definitions can be created from data stored in a database, from internal logic or by sending requests to the actuator gateway API. This guide provides you examples on how to create route definitions using the actuator gateway API .
Create a Route Definition via POST
Sending a POST request to the actuator gateway API is one way to create a route.
Be aware that posting a route containing an ID which already exists will overwrite the existing route definition.
This means that if you want to update a route which already have filters or predicates you want to keep, you have to make sure to also add these filters and predicates values to the request data.
The following snippet contains a request skeleton for creating a new route definition:bash
curl --location --request POST
'http://{DISPATCHER_HOST}/actuator/gateway/routes/{ROUTE_ID}'
--header
'Content-Type: application/json'
--data-raw '{
"id"
:
"{ROUTE_DEFINITION_ID}"
,
"predicates"
: [
{
"name"
:
"{PREDICATE_NAME}"
,
"args"
: {
"{KEY_1}"
:
"{VALUE_1}"
}
}
],
"filters"
: [
{
"name"
:
"{FILTER_NAME}"
,
"args"
: {
"{KEY_1}"
:
"{VALUE_1}"
}
}
],
"uri"
:
"http://example.com"
,
"order"
: 1
}'
After posting the defined route can be retrieved via GET request like already shown in the Usage section.
Keep in mind that internally the gateway implementation includes some caching features for routes. So manually refreshing the routes is always a good idea like show in the Usage section.
... with specific Predicate
In order to get a first working example we will define a predicate for a route to the specific URI http://example.com . The predicate will be set up to match a specific path:bash
curl --location --request POST
'http://{DISPATCHER_HOST}/actuator/gateway/routes/exampleRoute'
--header
'Content-Type: application/json'
--data-raw '{
"id"
:
"exampleRoute"
,
"predicates"
: [
{
"name"
:
"Path"
,
"args"
:
{
"pattern"
:
"/example/**"
}
}
],
"filters"
: [ ],
"uri"
:
"http://example.com"
,
"order"
: 1
}'
After this route is defined every request against the Dispatcher containing a context path started by /example/ will be routed to http://example.com .
For more information about different predicates which can also be set within Dispatcher's application.yml, please refer to Route Predicate Factories.
... with specific Filter
Additionally we will now add a filter to the route definition to manipulate the request which will be routed. In the following example the additional header X-Request-Foo will be added:bash
curl --location --request POST
'http://{DISPATCHER_HOST}/actuator/gateway/routes/exampleRoute'
--header
'Content-Type: application/json'
--data-raw '{
"id"
:
"exampleRoute"
,
"predicates"
: [
{
"name"
:
"Path"
,
"args"
:
{
"pattern"
:
"/example/**"
}
}
],
"filters"
: [
{
"name"
:
"AddRequestHeader"
,
"args"
:
{
"name"
:
"X-Request-Foo"
,
"value"
:
"Bar"
}
}
],
"uri"
:
"http://example.com"
,
"order"
: 1
}'
For more information about the available filters please refer to GatewayFilter Factories.
Configure a Request Rate Limiter
If you want to limit the amount of incoming requests the RequestRateLimiter is the right choice. The current implementation of the Dispatcher service provides you to use the existing RequestRateLimiter of Spring (RequestRateLimiter GatewayFilter Factory).
The internal Spring implementation uses Redis together with a Token Bucket Algorithm. So this means you need a Redis server instance for the RequestRateLimiter. The implementation contains two arguments the replenishRate and the burstCapacity.
The replenishRate is how many requests per second do you want a user to be allowed to do, without any dropped requests. This is the rate that the token bucket is filled.
The burstCapacity is the maximum number of requests a user is allowed to do in a single second. This is the number of tokens the token bucket can hold.
Alternative explanation:
Imagine you are in a video game and your health bar equals the number of request. Then the burstCapacity is the maximum damage per second you can take and the replenishRate is the number of request per second that heals you after you got hurt. Incoming request can only be handled if you have health left.
This means:
-
a steady rate is accomplished by setting the same value in replenishRate and burstCapacity
-
temporary bursts can be allowed by setting burstCapacity higher than replenishRate, but the time to recover from the burst takes longer
If the limits are reached then the RequestRateLimiter filter will return HTTP 429 - Too Many Requests if a keyResolver was configured properly.
By default, if the KeyResolver does not find a key, requests are always denied with HTTP 403 - Forbidden. You can adjust this behavior by playing around with the spring.cloud.gateway.filter.request-rate-limiter.deny-empty-key ( true or false ) and spring.cloud.gateway.filter.request-rate-limiter.empty-key-status-code properties (please refer to the Spring documentation for that).
Dispatcher 1.1.0 automatically provides the following default KeyResolver implementation which helps you to be able to use the RequestRateLimiter functionality out of the box:
java
@Bean
KeyResolver userKeyResolver( )
{
return
exchange -> Mono.just(
"1"
);
}
As soon as this or any other KeyResolver implementation is provided you be able to progress with the setup and configure your RequestRateLimiter.
1.) Setup Redis Instance
To setup a RequestRateLimiter for a specific route you first need to make sure that your Redis instance is running and is defined within your application.yaml. This setting is optional as long as your Redis is located at the default port on localhost:6379 .
In case your Redis has a different host and/or port, make sure the Dispatcher knows where to search by configuring the host and port in the application.yml of Dispatcher:bash
spring:
redis:
host: localhost
port: 6379
If you do not choose to change the host and port of your Redis instance and decide to take the default port 6379 on localhost, you don't need to configure the Redis instance in the Dispatcher application.yml like above.
2.) Send POST request to Dispatcher
To create setup the RequestRateLimiter send the following POST request to the running Dispatcher: bash
curl --location --request POST
'http://{DISPATCHER_HOST}/actuator/gateway/routes/exampleRoute'
--header
'Content-Type: application/json'
--data-raw '{
"id"
:
"exampleRoute"
,
"predicates"
: [
{
"name"
:
"Path"
,
"args"
:
{
"pattern"
:
"/example/**"
}
}
],
"filters"
: [
{
"name"
:
"RequestRateLimiter"
,
"args"
:
{
"redis-rate-limiter.replenishRate"
: 10,
"redis-rate-limiter.burstCapacity"
: 20
}
}
],
"uri"
:
"http://example.com/"
,
"order"
: 1
}'
To make your changes persistent you can also define your route within the application.yml of Dispatcher like it is shown in Springs documentation about the Redis Rate Limiter .
3.) Refresh your routes
To refresh your freshly created route definition containing the RequestRateLimiter send a POST request against http://{DISPATCHER_HOST}/actuator/gateway/refresh like shown within the usage section.
4.) Test your Setup
To test your RequestRateLimiter setup you just need to send more request against your route in one second than you are allowed to do. The replenish rate and burst capacity will be written to the response headers x-ratelimit-replenish-rate and x-ratelimit-burst-capacity. Additionally you will get an important information within the header x-ratelimit-remaining about the amount of requests you have left within the next second. If the limits are reached you should get the response status HTTP 429 - Too Many Requests.
Further Information
For a deeper use of the Spring Cloud Gateway features please refer to the following documentation links :