Centralized Swagger in MicroServices with SpringBoot, Gateway and SpringFox Swagger
Asked Answered
P

2

6

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.

Plasmagel answered 29/5, 2019 at 21:58 Comment(0)
Z
2

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.

Zendavesta answered 25/7, 2019 at 17:57 Comment(0)
G
1

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;
  }
}
Grampositive answered 19/8, 2020 at 18:41 Comment(1)
can you show application.yml for this or some github link?Meyer

© 2022 - 2024 — McMap. All rights reserved.