Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Centralized Swagger in MicroServices with SpringBoot, Gateway and SpringFox Swagger

I user Spring Cloud Gateway witch is based on Webflux, in my MicroService envoirement every Service has a own Swagger with UI... No problems so far. Now i would centralize all Swagger to one in my Gateway, i found tons of example code for the Zuul Gateway... I think this would work. But cant find any example for the spring.cloud.Gateway witch is base on the reactive Webflux interface, with the 'springfox:springfox-spring-webflux:3.0.0-SNAPSHOT' dependencie i get a SwaggerUI but i can not centralize all of my Swagger from the other MicroService i dont know how to configure the SwaggerResourcesProvider...

I dont know if i should do an own MicroService (no Webflux) to handle all Swaggers, i think this would not be a problem. The Problem is the WebFlux thing :-)

Maybe someone has a recommendation should i make an own MicroService (this should work but I'm not sure if this is over the top). Or someone has a solution for my Problem.

like image 767
3Tra Avatar asked May 29 '19 21:05

3Tra


2 Answers

I've run into the same issue and I didn't find a clean solution because there hasn't integration between Swagger and Webflux yet.

On the other hand, I succeed to set up a minimal configuration to create a proxy for swagger.

First you need to create a controller exposing Swagger API and ressources :


@Controller
public class SwaggerController {
    private final JsonSerializer jsonSerializer;
    private final SwaggerResourcesProvider swaggerResources;

    @Autowired
    public SwaggerController(JsonSerializer jsonSerializer, SwaggerResourcesProvider swaggerResources) {
        this.jsonSerializer = jsonSerializer;
        this.swaggerResources = swaggerResources;
    }

    @RequestMapping({"/swagger-resources/configuration/security"})
    @ResponseBody
    public ResponseEntity<SecurityConfiguration> securityConfiguration() {
        return ResponseEntity.ok(SecurityConfigurationBuilder.builder().build());
    }

    @RequestMapping({"/swagger-resources/configuration/ui"})
    @ResponseBody
    public ResponseEntity<UiConfiguration> uiConfiguration() {
        return ResponseEntity.ok(UiConfigurationBuilder.builder().build());
    }

    @RequestMapping({"/swagger-resources"})
    @ResponseBody
    public ResponseEntity<List<SwaggerResource>> swaggerResources() {
        return ResponseEntity.ok(this.swaggerResources.get());
    }

    @RequestMapping(
        value = {"/v2/api-docs"},
        method = {RequestMethod.GET},
        produces = {"application/json", "application/hal+json"}
    )
    @ResponseBody
    public ResponseEntity<Json> getDocumentation() {
        Swagger swagger = new Swagger();
        return ResponseEntity.ok(this.jsonSerializer.toJson(swagger));
    }
}

Next you need a swagger configuration to create Swagger ressources from your microservices :

package com.stackoverflow.gateway.config;

import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import springfox.documentation.spring.web.json.JacksonModuleRegistrar;
import springfox.documentation.spring.web.json.JsonSerializer;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;

import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;

@Configuration
public class SwaggerConfig {

    private final GatewayProperties properties;

    public SwaggerConfig(GatewayProperties properties) {
        this.properties = properties;
    }

    @Bean
    public JsonSerializer jsonSerializer(List<JacksonModuleRegistrar> moduleRegistrars) {
        return new JsonSerializer(moduleRegistrars);
    }

    @Primary
    @Bean
    @Lazy
    public SwaggerResourcesProvider swaggerResourcesProvider() {
        return () -> properties.getRoutes().stream()
            .map(route -> createResource(route.getId(), getRouteLocation(route), "2.0"))
            .filter(Objects::nonNull)
            .collect(Collectors.toList());
    }
  
    // You will certainly need to edit this
    private String getRouteLocation(RouteDefinition route) {
        return Optional.ofNullable(route.getPredicates().get(0).getArgs().values().toArray()[0])
            .map(String::valueOf)
            .map(s -> s.replace("*", ""))
            .orElse(null);
    }

    private SwaggerResource createResource(String name, String location, String version) {
        SwaggerResource swaggerResource = new SwaggerResource();
        swaggerResource.setName(name);
        swaggerResource.setLocation(location + "/v2/api-docs");
        swaggerResource.setSwaggerVersion(version);
        return swaggerResource;
    }
}

I think this solution will not work if you use a Service Discovery server but for my case I don't need to because I have a config for spring.cloud.gateway.routes :

 - id: api-client
   uri: lb://client
   predicates:
      - Path=/api/client/**

This can easily adapted to fetch service location from Eureka or Consul.

Let me know if it works. And if someone found another alternative I'm interested.

like image 120
Martin Choraine Avatar answered Sep 30 '22 05:09

Martin Choraine


I used spring fox with spring cloud gateway to create the swagger UI.

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-boot-starter</artifactId>
  <version>3.0.0</version>
</dependency>
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import springfox.documentation.swagger.web.SwaggerResource;
import springfox.documentation.swagger.web.SwaggerResourcesProvider;

@Primary
@Configuration
public class SwaggerConfig implements SwaggerResourcesProvider {
  @Autowired
  private RouteLocator routeLocator;

  @Override
  public List<SwaggerResource> get() {
    List<SwaggerResource> resources = new ArrayList<>();

// I used service names with lowercase hence the toLowerCase.
// Remove the toLowerCase if you use service name with caps as is default with eureka.
    routeLocator.getRoutes().subscribe(route -> {
      String name = route.getId().split("_")[1];
      resources.add(swaggerResource(name, "/" + name.toLowerCase() + "/v3/api-docs", "1.0"));
    });

    return resources;
  }

  private SwaggerResource swaggerResource(final String name, final String location,
      final String version) {
    SwaggerResource swaggerResource = new SwaggerResource();
    swaggerResource.setName(name);
    swaggerResource.setLocation(location);
    swaggerResource.setSwaggerVersion(version);
    return swaggerResource;
  }
}
like image 36
Sudarshan Debnath Avatar answered Sep 30 '22 05:09

Sudarshan Debnath