Да, существует Spring способ сделать это:
Шаг 1. Добавление дополнительного соединителя Tomcat
Чтобы добавить порт для встроенного сервера, необходимо настроить дополнительный соединитель. Мы сделаем это, предоставив пользовательский WebServerFactoryCustomizer:
@Component
public class TomcatContainerCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Value("${swagger.port}")
private int swaggerPort;
@Override
public void customize(TomcatServletWebServerFactory factory) {
Connector swaggerConnector = new Connector();
swaggerConnector.setPort(swaggerPort);
factory.addAdditionalTomcatConnectors(swaggerConnector);
}
}
Теперь Tomcat прослушивает два порта, но обслуживает одинаковое содержимое на обоих из них. Нам нужно отфильтровать его.
Шаг 2. Добавление фильтра
Добавление фильтра сервлета довольно просто с FilterRegistrationBean. Его можно создать где угодно, я добавил его прямо в TomcatContainerCustomizer.
@Component
public class TomcatContainerCustomizer implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
@Value("${swagger.port}")
private int swaggerPort;
@Value("${swagger.paths}")
private List<String> swaggerPaths;
@Override
public void customize(TomcatServletWebServerFactory factory) {
Connector swaggerConnector = new Connector();
swaggerConnector.setPort(swaggerPort);
factory.addAdditionalTomcatConnectors(swaggerConnector);
}
@Bean
public FilterRegistrationBean<SwaggerFilter> swaggerFilterRegistrationBean() {
FilterRegistrationBean<SwaggerFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new SwaggerFilter());
filterRegistrationBean.setOrder(-100);
filterRegistrationBean.setName("SwaggerFilter");
return filterRegistrationBean;
}
private class SwaggerFilter extends OncePerRequestFilter {
private AntPathMatcher pathMatcher = new AntPathMatcher();
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
FilterChain filterChain) throws ServletException, IOException {
boolean isSwaggerPath = swaggerPaths.stream()
.anyMatch(path -> pathMatcher.match(path, httpServletRequest.getServletPath()));
boolean isSwaggerPort = httpServletRequest.getLocalPort() == swaggerPort;
if(isSwaggerPath == isSwaggerPort) {
filterChain.doFilter(httpServletRequest, httpServletResponse);
} else {
httpServletResponse.sendError(404);
}
}
}
}
Свойства swagger.port
и swagger.paths
настраиваются в приложении. Yaml:
server.port: 8080
swagger:
port: 8088
paths: |
/swagger-ui.html,
/webjars/springfox-swagger-ui/**/*,
/swagger-resources,
/swagger-resources/**/*,
/v2/api-docs
Итак пока все хорошо: swagger-ui обслуживается через порт 8088, наш API - на 8080. Но есть проблема: когда мы пытаемся подключиться к API от swagger-ui, запросы отправляются на 8088 вместо 8080.
Шаг 3. Настройка конфигурации SpringFox.
Swagger предполагает, что API работает на том же порту, что и Swagger-UI. Нам нужно явно указать порт:
@Value("${server.port}")
private int serverPort;
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.host("localhost:" + serverPort);
}
И последняя проблема: поскольку пользовательский интерфейс работает на порте, отличном от API, запросы считаются перекрестными. Нам нужно разблокировать их. Это можно сделать глобально:
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**/*").allowedOrigins("http://localhost:" + swaggerPort);
}
};
}
или добавив аннотации к контроллерам:
@CrossOrigin(origins = "http://localhost:${swagger.port}")
Используемые версии: SpringBoot 2.2.2.RELEASE, springfox-swagger2 2.9.2
Рабочий пример см. https://github.com/mafor/swagger-ui-port