I’ve been using Netflix Zuul for many years as a proxy for APIs. Some weeks ago I tried to bootstrap a new project and add the zuul starter via spring initializr and couldn’t find it anymore. After some research it seems that Spring Cloud has moved over to Cloud Gateway and discontinued Netflix Zuul. Maybe this was just a rebranding of the Spring team but tbh I don’t care and want to follow the Spring Cloud team with that.

You can add Cloud Gateway to your app with a simple starter:

1
2
3
4
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

Please make sure to use the appropriate version. It is included in the spring cloud starter parent which you can easily import into your project.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-parent</artifactId>
            <version>2021.0.0</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

With the starter in place you only need a starter class annotated with SpringBootApplication to start using it. Here a simple example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
server:
  port: 8090

spring:
  cloud:
    gateway:
      routes:
        - id: header_route
          uri: http://localhost:8080
          predicates:
            - Header=X-Tenant, mbodev
          filters:
            - AddRequestHeader=Authorization, Basic dXNlcm5hbWU6cGFzc3dvcmQ=
      default-filters:
        - name: CircuitBreaker
          args:
            name: fallbackCircuitBreaker
            fallbackUri: forward:/fallback
        - PreserveHostHeader
        - name: RequestSize
          args:
            maxSize: 5MB
        - SecureHeaders
        - DedupeResponseHeader=Vary Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_UNIQUE
      globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins:
              - "*"
            allowedMethods:
              - GET
              - POST
              - PUT
              - DELETE
              - HEAD
              - OPTIONS
            allowedHeaders:
              - Accept
              - Content-Type
              - Origin
              - X-Tenant
            allowCredentials: false
        add-to-simple-url-handler-mapping: true

This config would take all requests with a header “X-Tenant: mbodev” and add an authorization header when forwarding the request to the given uri http://localhost:8080. Further it adds some default filters like a CircuitBreaker, SecureHeaders, … Documentation of all these filters can be found in the official documentation of Cloud Gateway. If you copy these settings please be notified that CORS is set to allowedOrigins: “*”!

Also adding/writing custom filters is quite easy. So in general I appreciate Spring Cloud’s decision to move further with Cloud Gateway.

The example also includes a fallback in the circuit breaker config. Here the Kotlin sample I wrote for this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.http.server.reactive.ServerHttpResponse
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
import reactor.core.publisher.Mono

@RestController
class FallbackController {

    @GetMapping(path = ["/fallback"], produces = [MediaType.APPLICATION_JSON_VALUE])
    fun fallback(response: ServerHttpResponse): Mono<FallbackResponse> {
        response.statusCode = HttpStatus.INTERNAL_SERVER_ERROR;
        return Mono.just(
            FallbackResponse(
                title = response.statusCode!!.name,
                message = "the requested url isn't available at the moment",
                code = response.statusCode!!.value(),
            )
        )
    }

}

Here the used response model:

1
2
3
4
5
data class FallbackResponse(
    val title: String,
    val message: String,
    val code: Int,
)

Reference https://blog.coffeebeans.at/archives/1738