Spring Cloud Gateway's OpenAPI Configuration

hans·2021년 11월 23일
0

Spring Cloud Gateway

목록 보기
1/1

Goal

MSA 기반 프로젝트의 SCG(Spring Cloud GateWay)에서 micro-service의 Swagger (OpenAPI) 통합 구현 연동

Architecture

Registry Application (Eureka Server)
Client Applications
Spring cloud Gateway

Sample Code

RegistryApplication (Eureka Server)

build.gradle

ext {
    set('springCloudVersion', "2020.0.3")
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'io.projectreactor:reactor-test'
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
    }
}

application.yml

spring.application.name: registry

server.port: 8761

eureka:
  client:
    register-with-eureka: false
    fetch-registry: false

*Eureka clients will look for port 8761 by default.

RegistryApplication.java

@EnableEurekaServer
@SpringBootApplication
public class RegistryAppApplication {

    public static void main(String[] args) {
        SpringApplication.run(RegistryAppApplication.class, args);
    }

}

board service

build.gradle

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'

    ... 이하 생략
}

application.yml

spring:
  application:
    name: board
    
    ...이하 생략

Gateway Application

build.gradle

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    implementation 'org.springframework.cloud:spring-cloud-starter-gateway'
    implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
    implementation 'org.springframework.cloud:spring-cloud-starter-sleuth'

    implementation 'org.springdoc:springdoc-openapi-webflux-ui:1.5.10'

    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'io.projectreactor:reactor-test'
}

application.yml

spring.application:
  name: gateway
spring.output:
  ansi.enabled: always # colorize console logs

logging:
  file:
    name: ./logs/${spring.application.name}.log
  logback.rollingpolicy.max-history: 7
  level:
    root: INFO
server:
  port: 8080
  shutdown: graceful
#  forward-headers-strategy: framework # 
#  # use-forward-headers: true
#  error.whitelabel.enabled: false
#
##springdoc.show-actuator: true
#
management:
  endpoints:
    web:
      exposure:
        include:
          - "gateway"
  endpoint:
    gateway:
      enabled: true
    health:
      show-details: always

#springdoc.api-docs.path : /gw

spring.cloud.gateway:
  default-filters:
    - name: GlobalFilter
      args:
        baseMessage: Spring Cloud Gateway GlobalFilter
        preLogger: true
        postLogger: true
  routes:
    - id: openapi
      uri: http://localhost:${server.port}
      predicates:
        - Path=/v3/api-docs/**
      filters:
        - RewritePath=/v3/api-docs/(?<path>.*), /$\{path}/v3/api-docs
    - id: board-service
      uri: lb://board
      predicates:
        - Path=/board/**
      filters:
        - RewritePath=/board/(?<path>.*), /$\{path}
    - id: board2-service
      uri: lb://board2
      predicates:
        - Path=/board2/**
      filters:
        - RewritePath=/board2/(?<path>.*), /$\{path}

-service suffix is given to separate micro service routes and open-api route.

Path and Rewrite-path may defer by given environment.

For some reason, setting forward-headers-strategy: framework breaks swagger. Not setting at all has not given such side-effect so far.

default-filters is optional setting.

To enable swagger for gateway itself, you can simply add route with application name. Perhaps you could use variable as below.

- id: ${spring.application.name}-service
  uri: lb://${spring.application.name}
  predicates:
  - Path=/${spring.application.name}/**
  filters:
  - RewritePath=/${spring.application.name}/(?<path>.*), /$\{path}

SwaggerConfig.java

@Configuration
public class SwaggerConfig {

    @Bean
    public CommandLineRunner openApiGroups(
            RouteDefinitionLocator locator,
            SwaggerUiConfigParameters swaggerUiParameters) {
        return args -> locator
                .getRouteDefinitions().collectList().block()
                .stream()
                .map(RouteDefinition::getId)
                .filter(id -> id.matches(".*-service"))
                .map(id -> id.replace("-service", ""))
                .forEach(swaggerUiParameters::addGroup);
    }
}

This CommandLineRunner will be ran once after application context has loaded and started.

I added open-api route to application.yml to rewrite open-api request into corrent form.

RouterDefinitionLocater gives list of routes we'd like to add, filters non-service route, removes -service suffix, and finally add groups to SwaggerUiConfigParameters.

Test

localhost:8080/swagger-ui.html

API call test

마치며

The way of adding groups to openApi might differ in later releases. Please refer this post is using version 1.5.

If on version 1.2 or below, see this document.

Get more information from official document

Examples for using GroupedOpenApi here.

Original reference here.

1/22/2022 Update

Simplified my code while keeping concepts.

board service

application.yml

spring:
  application:
    name: board
    
springdoc.api-docs.path : /openapi/${spring.application.name}

...이하 생략

Gateway Application

application.yml

...
springdoc.api-docs.path : /openapi

spring.cloud.gateway:
  default-filters:
    - name: GlobalFilter
      args:
        baseMessage: Spring Cloud Gateway GlobalFilter
        preLogger: true
        postLogger: true
  routes:
    - id: ${spring.application.name}
      uri: lb://${spring.application.name}
      predicates:
        - Path=/openapi/${spring.application.name}
      filters:
        - RewritePath=/openapi/${spring.application.name}, /openapi
    - id: sign
      uri: lb://sign
      predicates:
        - Path=/openapi/sign, /api/*/sign/**
    - id: board
      uri: lb://board
      predicates:
        - Path=/openapi/board, /api/*/board/**
  
  ...

SwaggerConfig.java

    ...
    @Bean
    public CommandLineRunner openApiGroups(RouteDefinitionLocator locator, SwaggerUiConfigParameters swaggerUiParameters) {
        return args -> locator
                .getRouteDefinitions().collectList().block()
                .stream()
                .map(RouteDefinition::getId)
                .forEach(swaggerUiParameters::addGroup);
    }
    ...
profile
응애 개발자

2개의 댓글

comment-user-thumbnail
알 수 없음
2022년 1월 22일
수정삭제

삭제된 댓글입니다.

1개의 답글
comment-user-thumbnail
2022년 9월 5일

Can you share your github repo? I get this error
Failed to load API definition.
Fetch error
Not Found /openapi/gateway/attendance

답글 달기