Spring Cloud Zuul and Spring Cloud Gateway to build an enriched gateway

Robert Diers
NEW IT Engineering
Published in
5 min readMay 2, 2022
Unsplash.com by Sam Moghadam Khamseh

Motivation

Building a reusable gateway with enhanced features like REST/SOAP conversation, JSON/XSD schema validation and custom auth based on Spring Boot. (sadly no chance to use products like apigee or kong)

We are going to configure the container/jar only for real use cases, but we do need the possibility to create custom filters.

Where are we coming from?

Highly regulated environment with a lot of standards — initially we were forced to use a bit of an outdated Spring Boot version. As Spring Cloud Gateway was not supported we started building the gateway with Spring Cloud Zuul.

Spring Cloud Zuul

Code Setup is really simple, setting up a route will take seconds. Here’s an example:

zuul.routes.route1.path=/books/**
zuul.routes.route1.url=http://localhost:50002/myapp/dummy

Ratelimits could be realized using a distributed cache or a JPA datasource — especially the last one could be realized with an in-memory H2 database — +1 for Zuul :-)

There were 2 points I didn’t like about the solution:

  • startup time — compared to a normal Spring Boot apps it takes a long time, sometimes minutes to start up… But this is not that important for the real use
  • “workarounds” to solve our enriched features — it reduced the flexibility to extend the gateway in the future and decreases the simplicity of the code

For example I had to create an additional configuration for the routes to configure our features:

zuul.routes.route2.path=/books2/**
zuul.routes.route2.url=http://localhost:50002/myapp/dummy
myapp.routes.route2.addheaders[0]=testheader1:hallowelt1
myapp.routes.route2.addheaders[1]=testheader2:hallowelt2
myapp.routes.route2.addheaders[2]=testheader3:hallowelt3
myapp.routes.route2.removeheaders[0]=testheader3
myapp.routes.route2.removeheaders[1]=robert
myapp.routes.route2.authurl=http://...
myapp.routes.route2.authparam1=...

For JSON/XSD Schema validation I had to read the payload and here the games have started:

  • using a stored variable from rate limit to get access to the request payload
  • something completely custom to read the response
  • no good approach to modify them (required for REST/SOAP conversion)

Zuul is not designed to include many custom extensions — I’m not talking about logging or other read-only activities. Changing the payload is the issue.

Spring Cloud Gateway

Jumping to Spring Boot 2.6.6 enabled us to use Spring Cloud Gateway, so I tried to migrate our “works — but not ideal” Zuul solution.

Configuration is a bit different, but again you can realize a route nearly without code:

- id: route1
uri: http://localhost:38080/
predicates:
- Path=/books/**

You can use a lot of pre-defined filters to achieve your goals:

- id: route2
uri: http://localhost:38080/
predicates:
- Path=/books2/**
filters:
- AddRequestHeader=testheader1, hallowelt1
- RemoveRequestHeader=robert
- RewritePath=/books2/(?<segment>.*),/books/$\{segment}

I do like the approach of concatenating filters to solve our problems, but we also need to take a look at the logic to modify request and response.

Up to now we have use pre-defined filters, that works out-of-the-box.

There is one disadvantage if you want to modify the payload, but it turned out to be an advantage for us. You need to create a class implementing RewriteFunction<String, String> and you have to register it to the route programmatically. Sadly this cannot be combined with pre-existing and pre-registered filters, they need to be registered programmatically, too (for this route).

So I created an interface ‘FilterBuilder’ all filters have to implement, they need to provide the logic to register themselfs.

public void extendFilter(GatewayFilterSpec f, FilterDefinition fd, RouteDefinition rd);

As all of them will be a Spring Boot component, I can iterate at startup and call the register method for each of them:

@Autowired
private List<FilterBuilder> builders;

Each required filter needs to be created — even the easiest ones:

(just reusing the pre-existing logic to add a header)

Using this approach we must explicitly allow the usage of a filter, because we have to create a builder for it. Really helpful if you need to force projects to stay within allowed usage scenarios.

Let’s have a look at a more powerful filter to convert REST to SOAP:

filters:
- name: Rest2Soap2Rest
args:
soapstart: ‘<soapenv:Envelope xmlns:soapenv=”http://schemas.xmlsoap.org/soap/envelope/" xmlns:soap=”http://soapexample/"><soapenv:Header/><soapenv:Body>'
soapend: ‘</soapenv:Body></soapenv:Envelope>’

Rest2Soap2Rest filter will care about the modification of content-type header and it will cut the “last” part of the URL as this is used as method within SOAP:

Builder class will create filter/config and it will register including two rewrite functions:

Our custom filter config:

Two Rewrite functions — here’s one of them to get an idea:

(add your code after this block)
(add new content with Mono.just)

Anything else? Yes — Rate limiter is based on Redis:

filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 3
redis-rate-limiter.burstCapacity: 5

I will continue the work with Spring Cloud Gateway as the “combining decoupled filters approach” seems to be the right one for me — hopefully the provided info was helpful for you :-)

--

--