Due to the division of microservices, interface documents generated by Swagger are also disassembled. Front-end colleagues have to save interface documents of each microservice as browser labels for quick switching. After the introduction of gateway, we want to improve this problem by unifying the entry of multiple microservices interface documents. It is better not to expose each microservice to the Internet, to configure whether to enable the interface document function uniformly, and to configure routing rules for the interface document.

Integrate WebFlux Swagger

The premise of developing microservices Gateway based on Spring Cloud Gateway is that we already know responsive programming and will use apis provided by Project Reactor and WebFlux. Integrating Swagger in a gateway project is essentially integrating Swagger in a WebFlux project.

The first step is to add Swagger dependencies to your project, choosing the version number.

Since the Spring Boot version number used in our project is 2.3.0.release, the Swagger version number we choose is 2.10.x.

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-spring-webflux</artifactId>
    <version>2.10.5</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.10.5</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.10.5</version>
</dependency>
Copy the code

You then need to add a static resource file access path map to WebFlux, that is, a ResourceWebHandler.

@EnableSwagger2WebFlux
@Configuration
public class WebfluxConfiguration implements WebFluxConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/swagger-ui.html**")
                .addResourceLocations("classpath:/META-INF/resources/");
        registry.addResourceHandler("/webjars/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/"); }}Copy the code

So far we have just configured Swagger for a WebFlux project. Now open a browser and go to/Swagger-ui.html #/ and all you see is the interface document Swagger generated for the gateway project.

Merge multiple microservice Swagger interface documents

Plan 1 (I saw it from some blogs)

Customize The SwaggerResourcesProvider in the gateway project to replace the One provided by Swagger.

Customize SwaggerResourcesProvider To implement the GET method of the SwaggerResourcesProvider interface. The method can return multiple SwaggerResources. Each SwaggerResource corresponds to each microservice. We can filter out the gateway itself, as follows.

@Primary
@Profile({"dev", "test"}) // Only local test and test environments are enabled
@Component
public class GatewaySwaggerResourcesProvider implements SwaggerResourcesProvider{
    @Override
    public List<SwaggerResource> get(a) {
        List<SwaggerResource> swaggerResources = new ArrayList<>();
        routeProperties.getRoutes().forEach(routeDefinition -> {
            String routeId = routeDefinition.getId();
            String baseUrl;
            if (isLocalDebug) {
                 baseUrl = "http://127.0.0.1:" + routeDefinition.getUri().getPort();
            } else{baseUrl = ${gateway domain name} +"/"+ ${route prefix}; } swaggerResources.add(getSwaggerResources(routeId, baseUrl)); });return swaggerResources;
    }

    private SwaggerResource getSwaggerResources(String name, String baseUrl) {
            SwaggerResource resource = new SwaggerResource();
            resource.setName(name);
            resource.setLocation(baseUrl + "/v2/api-docs");
            resource.setSwaggerVersion("2.0");
            returnresource; }}Copy the code

These SwaggerResources are the “Select a Definition” options we see in Swagger UI, as shown below.

About SwaggerResource:

  • Name: Swagger resource name, microservice name, also the name of the option we see in Swagger UI;
  • Location: corresponding to the microservice interface document/v2/api-docsThe URL of the API (i.ehttps://{gateway host}/{routing rules for routing to the application}/v2/api-docs).

Just joining together the following code in GatewaySwaggerResourcesProvider interface document baseUrl, meet “baseUrl + / v2 / API docs” can directly in the browser to access.

String baseUrl;
if (isLocalDebug) {
   baseUrl = "http://127.0.0.1:" + routeDefinition.getUri().getPort();
} else{baseUrl = ${gateway domain name} +"/"+ ${routing rules to the application}; }Copy the code

Because the location of SwaggerResource configuration is directly requested by the front-end, rather than by the gateway to obtain the request and then respond to the front-end, routing rules of “/v2/ apI-docs” interface need to be configured on the gateway for each micro-service.

Finally, other microservices need to be configured to support cross-domain requests, otherwise the Swagger front end cannot call the location configured by SwaggerResource back end service to initiate “/v2/ apI-docs” interface request.

Add cross-domain requests to other microservices as follows (note: not at the gateway!) .

@Profile({"dev", "test"}) // Test the environment only
@ConditionalOnClass(WebMvcConfigurer.class)
@Configuration
public class CorsAutoConfiguration {

    private CorsConfiguration buildConfig(a) {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.addAllowedOrigin("*");
        corsConfiguration.addAllowedHeader("*");
        corsConfiguration.addAllowedMethod("*");
        return corsConfiguration;
    }

    @Bean
    public CorsFilter corsFilter(a) {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/ * *", buildConfig());
        return newCorsFilter(source); }}Copy the code

Scheme 2

Although solution 1 is feasible, the drawback is to add Swagger’s “/v2/ apI-docs” interface routing configuration for each microservice. Instead, we use our own solution: request the “/v2/api-docs” interface for each microservice on the gateway proxy back end.

/v2/api-docs = /v2/api-docs = /v2/api-docs For example, if the location of SwaggerResource pointing to the user center is https:// gateway host/proxySwagger/v2/api-docs? RouteId = usercenter.” Then, the gateway provides /proxySwagger/v2/ apI-docs interface to initiate /v2/api-docs interface requests based on routeId for the back-end microservice, and directly respond the response results to the front-end.

GatewaySwaggerResourcesProvider code implementation is as follows:

@Primary
@Profile({"dev", "test"}) // Only local test and test environments are enabled
@Component
public class GatewaySwaggerResourcesProvider implements SwaggerResourcesProvider {

    @Resource
    private RouteProperties routeProperties;
    @ Value (" ${server. The domain: http://127.0.0.1:8600} ")
    private String gatewayDomain;

    @Override
    public List<SwaggerResource> get(a) {
        List<SwaggerResource> swaggerResources = new ArrayList<>();
        // Iterate over the route definition
        routeProperties.getRoutes().forEach(routeDefinition -> {
            String routeId = routeDefinition.getId();
            // Parameter 1: indicates the route ID
            // Parameter 2: indicates the gateway domain name
            swaggerResources.add(getSwaggerResources(routeId, gatewayDomain));
        });
        return swaggerResources;
    }

    private SwaggerResource getSwaggerResources(String routeId, String baseUrl) {
        SwaggerResource resource = new SwaggerResource();
        resource.setName(routeId);
        resource.setLocation(baseUrl + "/proxySwagger/v2/api-docs? routeId=" + routeId);
        resource.setSwaggerVersion("2.0");
        returnresource; }}Copy the code

ProxySwagger /v2/api-docs? RouteId =${routeId}

@Profile({"dev", "test"})
@RestController
@RequestMapping("/proxySwagger")
public class GatewayApiDocsController {

    @Resource
    private RouteProperties routeProperties;
    private WebClient webClient;
    private boolean isLocalDebug;

    @PostConstruct
    public void init(a) { webClient = WebClient.create(); isLocalDebug = .... ;// dev: true, test: false
    }

    @GetMapping("/v2/api-docs")
    public Mono<String> proxyApiDocs(@RequestParam("routeId") String routeId) {
        // Get the route configuration based on the route ID
        RouteDefinition routeDefinition = routeProperties.getRoutes().stream()
                .filter(rd -> rd.getId().equals(routeId))
                .findFirst().get();
        String baseUrl;
        URI routeUri = routeDefinition.getUri();
        if (isLocalDebug) {
            / / local debug
            baseUrl = "http://127.0.0.1";
        } else {
            baseUrl = "http://" + routeUri.getHost();
        }
        if (routeUri.getPort() > 0) {
            baseUrl += (":" + routeUri.getPort());
        }
        // Forward to the backend microservice
        return webClient.get()
                .uri(baseUrl + "/v2/api-docs") .retrieve() .bodyToMono(String.class); }}Copy the code
  • Note: Since our project is deployed on K8S, we have removed service registration/discovery, and all the routing rule configuration URIs are already directly accessible to the POD inside the K8S cluster.

Compared with the first scheme, this scheme has the following advantages:

  • 1. Strictly unify the traffic entrance and prevent Swagger’s front end from bypassing the gateway to directly access the back-end microservice interface;
  • 2. It is not necessary to require every microservice to be configured to support cross-domain requests;
  • 3. Don’t configure routing rules for Swagger API.