From 5cf6ab54056349249e9ee8fa2db7d5427853365d Mon Sep 17 00:00:00 2001 From: James Missen <88438228+jamesmissen@users.noreply.github.com> Date: Wed, 19 Nov 2025 03:24:15 +1300 Subject: [PATCH 1/6] Remove springdoc.webjars.prefix property --- .../AbstractSwaggerUiConfigProperties.java | 4 +- .../properties/SpringDocConfigProperties.java | 54 ------------------- .../webflux/ui/SwaggerWebFluxConfigurer.java | 26 +++------ .../webflux/ui/SwaggerWelcomeCommon.java | 24 +++------ .../ui/app18/SpringDocApp18Test.java | 5 +- .../SpringDocApp3RedirectWithPrefixTest.java | 7 ++- .../SpringDocBehindProxyBasePathTest.java | 5 +- .../src/test/resources/results/app18-1.json | 2 +- .../src/test/resources/results/app32-1.json | 2 +- .../src/test/resources/results/app33.json | 2 +- 10 files changed, 25 insertions(+), 106 deletions(-) diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/AbstractSwaggerUiConfigProperties.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/AbstractSwaggerUiConfigProperties.java index 49e8fe065..b39a6c0cb 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/AbstractSwaggerUiConfigProperties.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/AbstractSwaggerUiConfigProperties.java @@ -48,7 +48,7 @@ public abstract class AbstractSwaggerUiConfigProperties { /** - * The path for the Swagger UI pages to load. Will redirect to the springdoc.webjars.prefix property. + * The path for the Swagger UI pages to load. */ protected String path = Constants.DEFAULT_SWAGGER_UI_PATH; @@ -814,4 +814,4 @@ public String toString() { } } -} \ No newline at end of file +} diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/SpringDocConfigProperties.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/SpringDocConfigProperties.java index 1ea92f1ff..12425bc9d 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/SpringDocConfigProperties.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/properties/SpringDocConfigProperties.java @@ -44,7 +44,6 @@ import org.springframework.context.annotation.Lazy; import org.springframework.http.MediaType; -import static org.springdoc.core.utils.Constants.DEFAULT_WEB_JARS_PREFIX_URL; import static org.springdoc.core.utils.Constants.SPRINGDOC_ENABLED; /** @@ -64,11 +63,6 @@ public class SpringDocConfigProperties { */ private boolean showActuator; - /** - * The Webjars. - */ - private Webjars webjars = new Webjars(); - /** * The Api docs. */ @@ -786,24 +780,6 @@ public void setShowActuator(boolean showActuator) { this.showActuator = showActuator; } - /** - * Gets webjars. - * - * @return the webjars - */ - public Webjars getWebjars() { - return webjars; - } - - /** - * Sets webjars. - * - * @param webjars the webjars - */ - public void setWebjars(Webjars webjars) { - this.webjars = webjars; - } - /** * Gets api docs. * @@ -1390,36 +1366,6 @@ public void setEnabled(boolean enabled) { } } - /** - * The type Webjars. - * - * @author bnasslahsen - */ - public static class Webjars { - /** - * The Prefix. - */ - private String prefix = DEFAULT_WEB_JARS_PREFIX_URL; - - /** - * Gets prefix. - * - * @return the prefix - */ - public String getPrefix() { - return prefix; - } - - /** - * Sets prefix. - * - * @param prefix the prefix - */ - public void setPrefix(String prefix) { - this.prefix = prefix; - } - } - /** * The type Api docs. * diff --git a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWebFluxConfigurer.java b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWebFluxConfigurer.java index 1be092419..3bda90b44 100644 --- a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWebFluxConfigurer.java +++ b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWebFluxConfigurer.java @@ -32,12 +32,13 @@ import org.springdoc.core.properties.SwaggerUiConfigProperties; import org.springdoc.core.providers.ActuatorProvider; +import org.springframework.http.CacheControl; import org.springframework.web.reactive.config.ResourceHandlerRegistry; import org.springframework.web.reactive.config.WebFluxConfigurer; -import static org.springdoc.core.utils.Constants.ALL_PATTERN; import static org.springdoc.core.utils.Constants.CLASSPATH_RESOURCE_LOCATION; import static org.springdoc.core.utils.Constants.DEFAULT_WEB_JARS_PREFIX_URL; +import static org.springdoc.core.utils.Constants.SWAGGER_INITIALIZER_JS; import static org.springdoc.core.utils.Constants.SWAGGER_UI_PREFIX; import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR; @@ -102,28 +103,15 @@ public void addResourceHandlers(ResourceHandlerRegistry registry) { if (actuatorProvider.isPresent() && actuatorProvider.get().isUseManagementPort()) uiRootPath.append(actuatorProvider.get().getBasePath()); - String webjarsPrefix = springDocConfigProperties.getWebjars().getPrefix(); - String resourcePath, swaggerUiPrefix, swaggerUiWebjarsPrefix; - - if (DEFAULT_WEB_JARS_PREFIX_URL.equals(webjarsPrefix)) { - swaggerUiPrefix = SWAGGER_UI_PREFIX; - resourcePath = webjarsPrefix + SWAGGER_UI_PREFIX + DEFAULT_PATH_SEPARATOR + swaggerUiConfigProperties.getVersion() + DEFAULT_PATH_SEPARATOR; - swaggerUiWebjarsPrefix = webjarsPrefix + swaggerUiPrefix; - } - else { - swaggerUiPrefix = webjarsPrefix; - resourcePath = DEFAULT_WEB_JARS_PREFIX_URL + DEFAULT_PATH_SEPARATOR; - swaggerUiWebjarsPrefix = swaggerUiPrefix; - } - - registry.addResourceHandler(uiRootPath + swaggerUiWebjarsPrefix + ALL_PATTERN) - .addResourceLocations(CLASSPATH_RESOURCE_LOCATION + resourcePath) + registry.addResourceHandler(uiRootPath + SWAGGER_UI_PREFIX + "*/*" + SWAGGER_INITIALIZER_JS) + .addResourceLocations(CLASSPATH_RESOURCE_LOCATION + DEFAULT_WEB_JARS_PREFIX_URL + DEFAULT_PATH_SEPARATOR) + .setCacheControl(CacheControl.noStore()) .resourceChain(false) .addResolver(swaggerResourceResolver) .addTransformer(swaggerIndexTransformer); - registry.addResourceHandler(uiRootPath + swaggerUiPrefix + ALL_PATTERN) - .addResourceLocations(CLASSPATH_RESOURCE_LOCATION + resourcePath) + registry.addResourceHandler(uiRootPath + SWAGGER_UI_PREFIX + "*/**") + .addResourceLocations(CLASSPATH_RESOURCE_LOCATION + DEFAULT_WEB_JARS_PREFIX_URL + DEFAULT_PATH_SEPARATOR) .resourceChain(false) .addResolver(swaggerResourceResolver) .addTransformer(swaggerIndexTransformer); diff --git a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeCommon.java b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeCommon.java index 87660e686..4cf80ccfd 100644 --- a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeCommon.java +++ b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeCommon.java @@ -42,8 +42,6 @@ import org.springframework.web.util.ForwardedHeaderUtils; import org.springframework.web.util.UriComponentsBuilder; -import static org.springdoc.core.utils.Constants.DEFAULT_WEB_JARS_PREFIX_URL; - /** * The type Swagger welcome common. * @@ -72,15 +70,12 @@ protected SwaggerWelcomeCommon(SwaggerUiConfigProperties swaggerUiConfig, Spring protected Mono redirectToUi(ServerHttpRequest request, ServerHttpResponse response) { SwaggerUiConfigParameters swaggerUiConfigParameters = new SwaggerUiConfigParameters(swaggerUiConfig); buildFromCurrentContextPath(swaggerUiConfigParameters, request); - String webjarsPrefix = springDocConfigProperties.getWebjars().getPrefix(); - String additionalPrefix = DEFAULT_WEB_JARS_PREFIX_URL.equals(webjarsPrefix) ? "" : webjarsPrefix; - String sbUrl = swaggerUiConfigParameters.getContextPath() - + swaggerUiConfigParameters.getUiRootPath() - + additionalPrefix - + getSwaggerUiUrl(); + String sbUrl = swaggerUiConfigParameters.getContextPath() + swaggerUiConfigParameters.getUiRootPath() + getSwaggerUiUrl(); UriComponentsBuilder uriBuilder = getUriComponentsBuilder(swaggerUiConfigParameters, sbUrl); + // forward all queryParams from original request request.getQueryParams().forEach(uriBuilder::queryParam); + response.setStatusCode(HttpStatus.FOUND); response.getHeaders().setLocation(URI.create(uriBuilder.build().encode().toString())); return response.setComplete(); @@ -89,16 +84,9 @@ protected Mono redirectToUi(ServerHttpRequest request, ServerHttpResponse @Override protected void calculateOauth2RedirectUrl(SwaggerUiConfigParameters swaggerUiConfigParameters, UriComponentsBuilder uriComponentsBuilder) { if (StringUtils.isBlank(swaggerUiConfig.getOauth2RedirectUrl()) || !swaggerUiConfigParameters.isValidUrl(swaggerUiConfig.getOauth2RedirectUrl())) { - String webjarsPrefix = springDocConfigProperties.getWebjars().getPrefix(); - String additionalPath = DEFAULT_WEB_JARS_PREFIX_URL.equals(webjarsPrefix) ? "" : webjarsPrefix; - swaggerUiConfigParameters.setOauth2RedirectUrl( - uriComponentsBuilder - .path(swaggerUiConfigParameters.getUiRootPath()) - .path(additionalPath) - .path(getOauth2RedirectUrl()) - .build() - .toString() - ); + swaggerUiConfigParameters.setOauth2RedirectUrl(uriComponentsBuilder + .path(swaggerUiConfigParameters.getUiRootPath()) + .path(getOauth2RedirectUrl()).build().toString()); } } diff --git a/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app18/SpringDocApp18Test.java b/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app18/SpringDocApp18Test.java index 1a16f01e3..f64e295f3 100644 --- a/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app18/SpringDocApp18Test.java +++ b/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app18/SpringDocApp18Test.java @@ -45,8 +45,7 @@ properties = { "spring.webflux.base-path=/test", "server.port=9218", "springdoc.swagger-ui.path=/documentation/swagger-ui.html", - "springdoc.api-docs.path=/documentation/v3/api-docs", - "springdoc.webjars.prefix= /webjars-pref" }) + "springdoc.api-docs.path=/documentation/v3/api-docs" }) class SpringDocApp18Test extends AbstractCommonTest { @LocalServerPort @@ -66,7 +65,7 @@ void testIndex() throws Exception { .exchangeToMono(clientResponse -> Mono.just(clientResponse.statusCode())).block(); assertThat(httpStatusMono).isEqualTo(HttpStatus.FOUND); - httpStatusMono = webClient.get().uri("/test/documentation/webjars-pref/swagger-ui/index.html") + httpStatusMono = webClient.get().uri("/test/documentation/swagger-ui/index.html") .exchangeToMono(clientResponse -> Mono.just(clientResponse.statusCode())).block(); assertThat(httpStatusMono).isEqualTo(HttpStatus.OK); diff --git a/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app3/SpringDocApp3RedirectWithPrefixTest.java b/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app3/SpringDocApp3RedirectWithPrefixTest.java index c93d0744d..97cb15582 100644 --- a/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app3/SpringDocApp3RedirectWithPrefixTest.java +++ b/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app3/SpringDocApp3RedirectWithPrefixTest.java @@ -28,8 +28,7 @@ @TestPropertySource(properties = { "springdoc.swagger-ui.path=/documentation/swagger-ui.html", - "springdoc.api-docs.path=/documentation/v3/api-docs", - "springdoc.webjars.prefix= /webjars-pref" + "springdoc.api-docs.path=/documentation/v3/api-docs" }) public class SpringDocApp3RedirectWithPrefixTest extends AbstractSpringDocTest { @@ -38,8 +37,8 @@ void shouldRedirectWithPrefix() { WebTestClient.ResponseSpec responseSpec = webTestClient.get().uri("/documentation/swagger-ui.html").exchange() .expectStatus().isFound(); responseSpec.expectHeader() - .value("Location", Matchers.is("/documentation/webjars-pref/swagger-ui/index.html")); - webTestClient.get().uri("/documentation/webjars-pref/swagger-ui/index.html").exchange() + .value("Location", Matchers.is("/documentation/swagger-ui/index.html")); + webTestClient.get().uri("/documentation/swagger-ui/index.html").exchange() .expectStatus().isOk(); webTestClient.get().uri("/documentation/v3/api-docs/swagger-config").exchange() .expectStatus().isOk().expectBody().jsonPath("$.validatorUrl").isEqualTo(""); diff --git a/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app33/SpringDocBehindProxyBasePathTest.java b/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app33/SpringDocBehindProxyBasePathTest.java index 9134b91df..22e9a9055 100644 --- a/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app33/SpringDocBehindProxyBasePathTest.java +++ b/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app33/SpringDocBehindProxyBasePathTest.java @@ -41,8 +41,7 @@ "server.forward-headers-strategy=framework", "server.port=9318", "springdoc.swagger-ui.path=/documentation/swagger-ui.html", - "springdoc.api-docs.path=/documentation/v3/api-docs", - "springdoc.webjars.prefix= /webjars-pref" }) + "springdoc.api-docs.path=/documentation/v3/api-docs" }) @Import(SpringDocConfig.class) public class SpringDocBehindProxyBasePathTest extends AbstractCommonTest { @@ -69,7 +68,7 @@ void testIndex() throws Exception { .exchangeToMono(clientResponse -> Mono.just(clientResponse.statusCode())).block(); assertThat(httpStatusMono).isEqualTo(HttpStatus.FOUND); - httpStatusMono = webClient.get().uri(WEBFLUX_BASE_PATH + "/documentation/webjars-pref/swagger-ui/index.html") + httpStatusMono = webClient.get().uri(WEBFLUX_BASE_PATH + "/documentation/swagger-ui/index.html") .header("X-Forwarded-Prefix", X_FORWARD_PREFIX) .exchangeToMono(clientResponse -> Mono.just(clientResponse.statusCode())).block(); assertThat(httpStatusMono).isEqualTo(HttpStatus.OK); diff --git a/springdoc-openapi-starter-webflux-ui/src/test/resources/results/app18-1.json b/springdoc-openapi-starter-webflux-ui/src/test/resources/results/app18-1.json index e9cf64c6e..305529a92 100644 --- a/springdoc-openapi-starter-webflux-ui/src/test/resources/results/app18-1.json +++ b/springdoc-openapi-starter-webflux-ui/src/test/resources/results/app18-1.json @@ -1,6 +1,6 @@ { "configUrl": "/test/documentation/v3/api-docs/swagger-config", - "oauth2RedirectUrl": "http://localhost:9218/test/documentation/webjars-pref/swagger-ui/oauth2-redirect.html", + "oauth2RedirectUrl": "http://localhost:9218/test/documentation/swagger-ui/oauth2-redirect.html", "urls": [ { "url": "/test/documentation/v3/api-docs/users", diff --git a/springdoc-openapi-starter-webflux-ui/src/test/resources/results/app32-1.json b/springdoc-openapi-starter-webflux-ui/src/test/resources/results/app32-1.json index c6f06f1f6..f811db83f 100644 --- a/springdoc-openapi-starter-webflux-ui/src/test/resources/results/app32-1.json +++ b/springdoc-openapi-starter-webflux-ui/src/test/resources/results/app32-1.json @@ -1,6 +1,6 @@ { "configUrl": "/path/prefix/documentation/v3/api-docs/swagger-config", - "oauth2RedirectUrl": "http://localhost:9318/path/prefix/documentation/webjars-pref/swagger-ui/oauth2-redirect.html", + "oauth2RedirectUrl": "http://localhost:9318/path/prefix/documentation/swagger-ui/oauth2-redirect.html", "url": "/path/prefix/documentation/v3/api-docs", "validatorUrl": "" } diff --git a/springdoc-openapi-starter-webflux-ui/src/test/resources/results/app33.json b/springdoc-openapi-starter-webflux-ui/src/test/resources/results/app33.json index 9d03a31f8..eef083258 100644 --- a/springdoc-openapi-starter-webflux-ui/src/test/resources/results/app33.json +++ b/springdoc-openapi-starter-webflux-ui/src/test/resources/results/app33.json @@ -1,6 +1,6 @@ { "configUrl": "/path/prefix/test/documentation/v3/api-docs/swagger-config", - "oauth2RedirectUrl": "http://localhost:9318/path/prefix/test/documentation/webjars-pref/swagger-ui/oauth2-redirect.html", + "oauth2RedirectUrl": "http://localhost:9318/path/prefix/test/documentation/swagger-ui/oauth2-redirect.html", "url": "/path/prefix/test/documentation/v3/api-docs", "validatorUrl": "" } From 2551a6fb4c73116a93f823c8dc461f536f75ad2d Mon Sep 17 00:00:00 2001 From: James Missen <88438228+jamesmissen@users.noreply.github.com> Date: Wed, 19 Nov 2025 03:30:27 +1300 Subject: [PATCH 2/6] Modify resource handlers to correctly use WebJar path pattern --- .../org/springdoc/core/utils/Constants.java | 41 ++++-- .../springdoc/webflux/ui/SwaggerConfig.java | 11 +- .../webflux/ui/SwaggerWebFluxConfigurer.java | 118 +++++++++++++---- .../springdoc/webmvc/ui/SwaggerConfig.java | 10 +- .../webmvc/ui/SwaggerWebMvcConfigurer.java | 120 +++++++++++++++--- 5 files changed, 235 insertions(+), 65 deletions(-) diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/Constants.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/Constants.java index 2a2e916f7..031d16f10 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/Constants.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/Constants.java @@ -26,9 +26,8 @@ package org.springdoc.core.utils; -import org.springframework.util.ResourceUtils; - import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR; +import static org.springframework.util.ResourceUtils.CLASSPATH_URL_PREFIX; /** * The type Constants. @@ -70,7 +69,7 @@ public final class Constants { /** * The constant SWAGGER_CONFIG_URL. */ - public static final String SWAGGER_CONFIG_URL = API_DOCS_URL + DEFAULT_PATH_SEPARATOR + SWAGGER_CONFIG_FILE; + public static final String SWAGGER_CONFIG_URL = API_DOCS_URL + "/" + SWAGGER_CONFIG_FILE; /** * The constant YAML. @@ -177,21 +176,25 @@ public final class Constants { */ public static final String SPRINGDOC_ACTUATOR_DOC_DESCRIPTION = "Spring Boot Actuator Web API Documentation"; - /** - * The constant DEFAULT_WEB_JARS_PREFIX_URL. - */ - public static final String DEFAULT_WEB_JARS_PREFIX_URL = "/webjars"; + /** + * The constant CLASSPATH_RESOURCE_LOCATION. + */ + public static final String CLASSPATH_RESOURCE_LOCATION = CLASSPATH_URL_PREFIX + "META-INF" + DEFAULT_PATH_SEPARATOR + "resources" + DEFAULT_PATH_SEPARATOR; - /** - * The constant CLASSPATH_RESOURCE_LOCATION. - */ - public static final String CLASSPATH_RESOURCE_LOCATION = ResourceUtils.CLASSPATH_URL_PREFIX + "/META-INF/resources"; + /** + * The constant WEBJARS_RESOURCE_LOCATION. + */ + public static final String WEBJARS_RESOURCE_LOCATION = CLASSPATH_RESOURCE_LOCATION + "webjars" + DEFAULT_PATH_SEPARATOR; + /** + * The constant SWAGGER_UI_WEBJAR_NAME. + */ + public static final String SWAGGER_UI_WEBJAR_NAME = "swagger-ui"; /** * The constant SWAGGER_UI_PREFIX. */ - public static final String SWAGGER_UI_PREFIX = "/swagger-ui"; + public static final String SWAGGER_UI_PREFIX = "/" + SWAGGER_UI_WEBJAR_NAME; /** * The constant INDEX_PAGE. @@ -231,7 +234,7 @@ public final class Constants { /** * The constant DEFAULT_SWAGGER_UI_PATH. */ - public static final String DEFAULT_SWAGGER_UI_PATH = DEFAULT_PATH_SEPARATOR + "swagger-ui.html"; + public static final String DEFAULT_SWAGGER_UI_PATH = "/swagger-ui.html"; /** * The constant SWAGGER_UI_PATH. @@ -363,6 +366,16 @@ public final class Constants { */ public static final String ALL_PATTERN = "/**"; + /** + * The constant SWAGGER_UI_WEBJAR_NAME_PATTERN. + */ + public static final String SWAGGER_UI_WEBJAR_NAME_PATTERN = "/*" + SWAGGER_UI_WEBJAR_NAME; + + /** + * The constant SWAGGER_INITIALIZER_PATTERN. + */ + public static final String SWAGGER_INITIALIZER_PATTERN = "/*" + SWAGGER_INITIALIZER_JS; + /** * The constant HEALTH_PATTERN. */ @@ -397,7 +410,7 @@ public final class Constants { /** * The constant DEFAULT_YAML_API_DOCS_ACTUATOR_PATH. */ - public static final String DEFAULT_YAML_API_DOCS_ACTUATOR_PATH = DEFAULT_PATH_SEPARATOR + YAML; + public static final String DEFAULT_YAML_API_DOCS_ACTUATOR_PATH = "/" + YAML; /** * The constant ACTUATOR_DEFAULT_GROUP. diff --git a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerConfig.java b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerConfig.java index f9f1eddb2..4dfccda79 100644 --- a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerConfig.java +++ b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerConfig.java @@ -47,6 +47,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; +import org.springframework.boot.autoconfigure.web.WebProperties; import org.springframework.boot.webflux.actuate.endpoint.web.WebFluxEndpointHandlerMapping; import org.springframework.boot.webflux.autoconfigure.WebFluxProperties; import org.springframework.context.annotation.Bean; @@ -121,7 +122,8 @@ SwaggerUiHome swaggerUiHome(Optional optionalWebFluxPropertie * Swagger web flux configurer swagger web flux configurer. * * @param swaggerUiConfigProperties the swagger ui calculated config - * @param springDocConfigProperties the spring doc config properties + * @param springWebProperties the spring web config + * @param springWebFluxProperties the spring webflux config * @param swaggerIndexTransformer the swagger index transformer * @param actuatorProvider the actuator provider * @param swaggerResourceResolver the swagger resource resolver @@ -131,9 +133,10 @@ SwaggerUiHome swaggerUiHome(Optional optionalWebFluxPropertie @ConditionalOnMissingBean @Lazy(false) SwaggerWebFluxConfigurer swaggerWebFluxConfigurer(SwaggerUiConfigProperties swaggerUiConfigProperties, - SpringDocConfigProperties springDocConfigProperties, SwaggerIndexTransformer swaggerIndexTransformer, - Optional actuatorProvider, SwaggerResourceResolver swaggerResourceResolver) { - return new SwaggerWebFluxConfigurer(swaggerUiConfigProperties, springDocConfigProperties, swaggerIndexTransformer, actuatorProvider, swaggerResourceResolver); + WebProperties springWebProperties, WebFluxProperties springWebFluxProperties, + SwaggerIndexTransformer swaggerIndexTransformer, Optional actuatorProvider, + SwaggerResourceResolver swaggerResourceResolver) { + return new SwaggerWebFluxConfigurer(swaggerUiConfigProperties, springWebProperties, springWebFluxProperties, swaggerIndexTransformer, actuatorProvider, swaggerResourceResolver); } /** diff --git a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWebFluxConfigurer.java b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWebFluxConfigurer.java index 3bda90b44..827254c04 100644 --- a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWebFluxConfigurer.java +++ b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWebFluxConfigurer.java @@ -27,19 +27,21 @@ package org.springdoc.webflux.ui; import java.util.Optional; - -import org.springdoc.core.properties.SpringDocConfigProperties; import org.springdoc.core.properties.SwaggerUiConfigProperties; import org.springdoc.core.providers.ActuatorProvider; - +import org.springframework.boot.autoconfigure.web.WebProperties; +import org.springframework.boot.webflux.autoconfigure.WebFluxProperties; import org.springframework.http.CacheControl; import org.springframework.web.reactive.config.ResourceHandlerRegistry; import org.springframework.web.reactive.config.WebFluxConfigurer; - -import static org.springdoc.core.utils.Constants.CLASSPATH_RESOURCE_LOCATION; -import static org.springdoc.core.utils.Constants.DEFAULT_WEB_JARS_PREFIX_URL; -import static org.springdoc.core.utils.Constants.SWAGGER_INITIALIZER_JS; +import org.springframework.web.util.pattern.PathPattern; +import org.springframework.web.util.pattern.PathPatternParser; +import static org.springdoc.core.utils.Constants.ALL_PATTERN; +import static org.springdoc.core.utils.Constants.SWAGGER_INITIALIZER_PATTERN; import static org.springdoc.core.utils.Constants.SWAGGER_UI_PREFIX; +import static org.springdoc.core.utils.Constants.SWAGGER_UI_WEBJAR_NAME; +import static org.springdoc.core.utils.Constants.SWAGGER_UI_WEBJAR_NAME_PATTERN; +import static org.springdoc.core.utils.Constants.WEBJARS_RESOURCE_LOCATION; import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR; /** @@ -70,51 +72,117 @@ public class SwaggerWebFluxConfigurer implements WebFluxConfigurer { private final SwaggerUiConfigProperties swaggerUiConfigProperties; /** - * The Spring doc config properties. + * The Spring Web config properties. */ - private final SpringDocConfigProperties springDocConfigProperties; + private final WebProperties springWebProperties; + + /** + * The Spring WebFlux config properties. + */ + private final WebFluxProperties springWebFluxProperties; + + private final PathPatternParser parser = new PathPatternParser(); /** * Instantiates a new Swagger web flux configurer. * * @param swaggerUiConfigProperties the swagger ui calculated config - * @param springDocConfigProperties the spring doc config properties + * @param springWebProperties the spring web config + * @param springWebFluxProperties the spring webflux config * @param swaggerIndexTransformer the swagger index transformer * @param actuatorProvider the actuator provider * @param swaggerResourceResolver the swagger resource resolver */ public SwaggerWebFluxConfigurer(SwaggerUiConfigProperties swaggerUiConfigProperties, - SpringDocConfigProperties springDocConfigProperties, - SwaggerIndexTransformer swaggerIndexTransformer, - Optional actuatorProvider, SwaggerResourceResolver swaggerResourceResolver) { + WebProperties springWebProperties, WebFluxProperties springWebFluxProperties, + SwaggerIndexTransformer swaggerIndexTransformer, Optional actuatorProvider, + SwaggerResourceResolver swaggerResourceResolver) { this.swaggerIndexTransformer = swaggerIndexTransformer; this.actuatorProvider = actuatorProvider; this.swaggerResourceResolver = swaggerResourceResolver; this.swaggerUiConfigProperties = swaggerUiConfigProperties; - this.springDocConfigProperties = springDocConfigProperties; + this.springWebProperties = springWebProperties; + this.springWebFluxProperties = springWebFluxProperties; } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { - StringBuilder uiRootPath = new StringBuilder(); - String swaggerPath = swaggerUiConfigProperties.getPath(); - if (swaggerPath.contains(DEFAULT_PATH_SEPARATOR)) - uiRootPath.append(swaggerPath, 0, swaggerPath.lastIndexOf(DEFAULT_PATH_SEPARATOR)); - if (actuatorProvider.isPresent() && actuatorProvider.get().isUseManagementPort()) - uiRootPath.append(actuatorProvider.get().getBasePath()); + String swaggerUiPattern = getUiRootPath() + SWAGGER_UI_PREFIX + ALL_PATTERN; + String swaggerUiResourceLocation = WEBJARS_RESOURCE_LOCATION + SWAGGER_UI_WEBJAR_NAME + DEFAULT_PATH_SEPARATOR + + swaggerUiConfigProperties.getVersion() + DEFAULT_PATH_SEPARATOR; - registry.addResourceHandler(uiRootPath + SWAGGER_UI_PREFIX + "*/*" + SWAGGER_INITIALIZER_JS) - .addResourceLocations(CLASSPATH_RESOURCE_LOCATION + DEFAULT_WEB_JARS_PREFIX_URL + DEFAULT_PATH_SEPARATOR) - .setCacheControl(CacheControl.noStore()) + addSwaggerUiResourceHandler(registry, swaggerUiPattern, swaggerUiResourceLocation); + + // Add custom mappings for Swagger UI WebJar resources if Spring resource mapping is enabled + if (springWebProperties.getResources().isAddMappings()) { + String webjarsPathPattern = springWebFluxProperties.getWebjarsPathPattern(); + + String swaggerUiWebjarPattern = mergePatterns(webjarsPathPattern, SWAGGER_UI_WEBJAR_NAME_PATTERN) + ALL_PATTERN; + String swaggerUiWebjarResourceLocation = WEBJARS_RESOURCE_LOCATION; + + addSwaggerUiResourceHandler(registry, swaggerUiWebjarPattern, swaggerUiWebjarResourceLocation); + } + } + + /** + * Adds the resource handlers for serving the Swagger UI resources. + */ + protected void addSwaggerUiResourceHandler(ResourceHandlerRegistry registry, String pattern, String... resourceLocations) { + registry.addResourceHandler(pattern) + .addResourceLocations(resourceLocations) .resourceChain(false) .addResolver(swaggerResourceResolver) .addTransformer(swaggerIndexTransformer); - registry.addResourceHandler(uiRootPath + SWAGGER_UI_PREFIX + "*/**") - .addResourceLocations(CLASSPATH_RESOURCE_LOCATION + DEFAULT_WEB_JARS_PREFIX_URL + DEFAULT_PATH_SEPARATOR) + // Ensure Swagger initializer has "no-store" Cache-Control header + registry.addResourceHandler(mergePatterns(pattern, SWAGGER_INITIALIZER_PATTERN)) + .setCacheControl(CacheControl.noStore()) + .addResourceLocations(resourceLocations) .resourceChain(false) .addResolver(swaggerResourceResolver) .addTransformer(swaggerIndexTransformer); } + /** + * Computes and returns the root path for the Swagger UI. + * + * @return the Swagger UI root path. + */ + protected String getUiRootPath() { + StringBuilder uiRootPath = new StringBuilder(); + + if (actuatorProvider.isPresent() && actuatorProvider.get().isUseManagementPort()) { + uiRootPath.append(actuatorProvider.get().getBasePath()); + } + + String swaggerUiPath = swaggerUiConfigProperties.getPath(); + if (swaggerUiPath.contains(DEFAULT_PATH_SEPARATOR)) { + uiRootPath.append(swaggerUiPath, 0, swaggerUiPath.lastIndexOf(DEFAULT_PATH_SEPARATOR)); + } + + return uiRootPath.toString(); + } + + /** + * Combines two patterns into a new pattern according to the rules of {@link PathPattern#combine}. + * + *

For example: + *

    + *
  • /webjars/** + /swagger-ui/** => /webjars/swagger-ui/**
  • + *
  • /documentation/swagger-ui*/** + /*.js => /documentation/swagger-ui*/*.js
  • + *
+ * + * @param pattern1 the first pattern + * @param pattern2 the second pattern + * + * @return the combination of the two patterns + * + * @see PathPattern#combine + */ + private String mergePatterns(String pattern1, String pattern2) { + PathPattern pathPattern1 = parser.parse(parser.initFullPathPattern(pattern1)); + PathPattern pathPattern2 = parser.parse(parser.initFullPathPattern(pattern2)); + + return pathPattern1.combine(pathPattern2).getPatternString(); + } } diff --git a/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerConfig.java b/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerConfig.java index 3a1aac8bd..1f5b74781 100644 --- a/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerConfig.java +++ b/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerConfig.java @@ -47,7 +47,9 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; +import org.springframework.boot.autoconfigure.web.WebProperties; import org.springframework.boot.webmvc.actuate.endpoint.web.WebMvcEndpointHandlerMapping; +import org.springframework.boot.webmvc.autoconfigure.WebMvcProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; @@ -148,6 +150,8 @@ SwaggerIndexTransformer indexPageTransformer(SwaggerUiConfigProperties swaggerUi * Swagger web mvc configurer swagger web mvc configurer. * * @param swaggerUiConfigProperties the swagger ui calculated config + * @param springWebProperties the spring web config + * @param springWebMvcProperties the spring mvc config * @param swaggerIndexTransformer the swagger index transformer * @param actuatorProvider the actuator provider * @param swaggerResourceResolver the swagger resource resolver @@ -157,8 +161,10 @@ SwaggerIndexTransformer indexPageTransformer(SwaggerUiConfigProperties swaggerUi @ConditionalOnMissingBean @Lazy(false) SwaggerWebMvcConfigurer swaggerWebMvcConfigurer(SwaggerUiConfigProperties swaggerUiConfigProperties, - SwaggerIndexTransformer swaggerIndexTransformer, Optional actuatorProvider, SwaggerResourceResolver swaggerResourceResolver) { - return new SwaggerWebMvcConfigurer(swaggerUiConfigProperties, swaggerIndexTransformer, actuatorProvider, swaggerResourceResolver); + WebProperties springWebProperties, WebMvcProperties springWebMvcProperties, + SwaggerIndexTransformer swaggerIndexTransformer, Optional actuatorProvider, + SwaggerResourceResolver swaggerResourceResolver) { + return new SwaggerWebMvcConfigurer(swaggerUiConfigProperties, springWebProperties, springWebMvcProperties, swaggerIndexTransformer, actuatorProvider, swaggerResourceResolver); } /** diff --git a/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWebMvcConfigurer.java b/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWebMvcConfigurer.java index 981eb4205..34fcda8b2 100644 --- a/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWebMvcConfigurer.java +++ b/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWebMvcConfigurer.java @@ -28,11 +28,12 @@ import java.util.List; import java.util.Optional; - import org.springdoc.core.properties.SwaggerUiConfigProperties; import org.springdoc.core.providers.ActuatorProvider; - +import org.springframework.boot.autoconfigure.web.WebProperties; +import org.springframework.boot.webmvc.autoconfigure.WebMvcProperties; import org.springframework.format.FormatterRegistry; +import org.springframework.http.CacheControl; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.lang.Nullable; import org.springframework.validation.MessageCodesResolver; @@ -50,11 +51,15 @@ import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.ViewResolverRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.util.pattern.PathPattern; +import org.springframework.web.util.pattern.PathPatternParser; -import static org.springdoc.core.utils.Constants.CLASSPATH_RESOURCE_LOCATION; -import static org.springdoc.core.utils.Constants.DEFAULT_WEB_JARS_PREFIX_URL; -import static org.springdoc.core.utils.Constants.SWAGGER_INITIALIZER_JS; +import static org.springdoc.core.utils.Constants.ALL_PATTERN; +import static org.springdoc.core.utils.Constants.SWAGGER_INITIALIZER_PATTERN; import static org.springdoc.core.utils.Constants.SWAGGER_UI_PREFIX; +import static org.springdoc.core.utils.Constants.SWAGGER_UI_WEBJAR_NAME; +import static org.springdoc.core.utils.Constants.SWAGGER_UI_WEBJAR_NAME_PATTERN; +import static org.springdoc.core.utils.Constants.WEBJARS_RESOURCE_LOCATION; import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR; /** @@ -74,56 +79,131 @@ public class SwaggerWebMvcConfigurer implements WebMvcConfigurer { */ private final Optional actuatorProvider; + /** + * The Swagger resource resolver. + */ + private final SwaggerResourceResolver swaggerResourceResolver; + /** * The Swagger ui config properties. */ private final SwaggerUiConfigProperties swaggerUiConfigProperties; /** - * The Swagger resource resolver. + * The Spring Web config properties. */ - private final SwaggerResourceResolver swaggerResourceResolver; + private final WebProperties springWebProperties; + + /** + * The Spring MVC config properties. + */ + private final WebMvcProperties springWebMvcProperties; + + private final PathPatternParser parser = new PathPatternParser(); /** * Instantiates a new Swagger web mvc configurer. * * @param swaggerUiConfigProperties the swagger ui calculated config + * @param springWebProperties the spring web config + * @param springWebMvcProperties the spring mvc config * @param swaggerIndexTransformer the swagger index transformer * @param actuatorProvider the actuator provider * @param swaggerResourceResolver the swagger resource resolver */ public SwaggerWebMvcConfigurer(SwaggerUiConfigProperties swaggerUiConfigProperties, - SwaggerIndexTransformer swaggerIndexTransformer, - Optional actuatorProvider, SwaggerResourceResolver swaggerResourceResolver) { + WebProperties springWebProperties, WebMvcProperties springWebMvcProperties, + SwaggerIndexTransformer swaggerIndexTransformer, Optional actuatorProvider, + SwaggerResourceResolver swaggerResourceResolver) { this.swaggerIndexTransformer = swaggerIndexTransformer; this.actuatorProvider = actuatorProvider; this.swaggerResourceResolver = swaggerResourceResolver; this.swaggerUiConfigProperties = swaggerUiConfigProperties; + this.springWebProperties = springWebProperties; + this.springWebMvcProperties = springWebMvcProperties; } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { - StringBuilder uiRootPath = new StringBuilder(); - String swaggerPath = swaggerUiConfigProperties.getPath(); - if (swaggerPath.contains(DEFAULT_PATH_SEPARATOR)) - uiRootPath.append(swaggerPath, 0, swaggerPath.lastIndexOf(DEFAULT_PATH_SEPARATOR)); - if (actuatorProvider.isPresent() && actuatorProvider.get().isUseManagementPort()) - uiRootPath.append(actuatorProvider.get().getBasePath()); + String swaggerUiPattern = getUiRootPath() + SWAGGER_UI_PREFIX + ALL_PATTERN; + String swaggerUiResourceLocation = WEBJARS_RESOURCE_LOCATION + SWAGGER_UI_WEBJAR_NAME + DEFAULT_PATH_SEPARATOR + + swaggerUiConfigProperties.getVersion() + DEFAULT_PATH_SEPARATOR; + + addSwaggerUiResourceHandler(registry, swaggerUiPattern, swaggerUiResourceLocation); - registry.addResourceHandler(uiRootPath + SWAGGER_UI_PREFIX + "*/*" + SWAGGER_INITIALIZER_JS) - .addResourceLocations(CLASSPATH_RESOURCE_LOCATION + DEFAULT_WEB_JARS_PREFIX_URL + DEFAULT_PATH_SEPARATOR) - .setCachePeriod(0) + // Add custom mappings for Swagger UI WebJar resources if Spring resource mapping is enabled + if (springWebProperties.getResources().isAddMappings()) { + String webjarsPathPattern = springWebMvcProperties.getWebjarsPathPattern(); + + String swaggerUiWebjarPattern = mergePatterns(webjarsPathPattern, SWAGGER_UI_WEBJAR_NAME_PATTERN) + ALL_PATTERN; + String swaggerUiWebjarResourceLocation = WEBJARS_RESOURCE_LOCATION; + + addSwaggerUiResourceHandler(registry, swaggerUiWebjarPattern, swaggerUiWebjarResourceLocation); + } + } + + /** + * Adds the resource handlers for serving the Swagger UI resources. + */ + protected void addSwaggerUiResourceHandler(ResourceHandlerRegistry registry, String pattern, String... resourceLocations) { + registry.addResourceHandler(pattern) + .addResourceLocations(resourceLocations) .resourceChain(false) .addResolver(swaggerResourceResolver) .addTransformer(swaggerIndexTransformer); - registry.addResourceHandler(uiRootPath + SWAGGER_UI_PREFIX + "*/**") - .addResourceLocations(CLASSPATH_RESOURCE_LOCATION + DEFAULT_WEB_JARS_PREFIX_URL + DEFAULT_PATH_SEPARATOR) + // Ensure Swagger initializer has "no-store" Cache-Control header + registry.addResourceHandler(mergePatterns(pattern, SWAGGER_INITIALIZER_PATTERN)) + .setCacheControl(CacheControl.noStore()) + .addResourceLocations(resourceLocations) .resourceChain(false) .addResolver(swaggerResourceResolver) .addTransformer(swaggerIndexTransformer); } + /** + * Computes and returns the root path for the Swagger UI. + * + * @return the Swagger UI root path. + */ + protected String getUiRootPath() { + StringBuilder uiRootPath = new StringBuilder(); + + if (actuatorProvider.isPresent() && actuatorProvider.get().isUseManagementPort()) { + uiRootPath.append(actuatorProvider.get().getBasePath()); + } + + String swaggerUiPath = swaggerUiConfigProperties.getPath(); + if (swaggerUiPath.contains(DEFAULT_PATH_SEPARATOR)) { + uiRootPath.append(swaggerUiPath, 0, swaggerUiPath.lastIndexOf(DEFAULT_PATH_SEPARATOR)); + } + + return uiRootPath.toString(); + } + + /** + * Combines two patterns into a new pattern according to the rules of {@link PathPattern#combine}. + * + *

For example: + *

    + *
  • /webjars/** + /swagger-ui/** => /webjars/swagger-ui/**
  • + *
  • /documentation/swagger-ui*/** + /*.js => /documentation/swagger-ui*/*.js
  • + *
+ * + * @param pattern1 the first pattern + * @param pattern2 the second pattern + * + * @return the combination of the two patterns + * + * @see PathPattern#combine + */ + private String mergePatterns(String pattern1, String pattern2) { + PathPattern pathPattern1 = parser.parse(parser.initFullPathPattern(pattern1)); + PathPattern pathPattern2 = parser.parse(parser.initFullPathPattern(pattern2)); + + return pathPattern1.combine(pathPattern2).getPatternString(); + } + @Override public void configurePathMatch(PathMatchConfigurer configurer) { // This implementation is empty to keep compatibility with spring 4 applications. From 81734425f435da39b3b28bcc602cdf0d97758ef3 Mon Sep 17 00:00:00 2001 From: James Missen <88438228+jamesmissen@users.noreply.github.com> Date: Wed, 19 Nov 2025 03:34:37 +1300 Subject: [PATCH 3/6] Add consistent tests for WebFlux and WebMvc to verify correct WebJar mappings --- .../ui/app34/SpringDocApp34Test.java | 7 ++- .../springdoc/ui/app35/HelloController.java | 41 ++++++++++++ .../ui/app35/SpringDocApp35Test.java | 63 +++++++++++++++++++ .../springdoc/ui/app36/HelloController.java | 41 ++++++++++++ .../ui/app36/SpringDocApp36Test.java | 53 ++++++++++++++++ .../springdoc/ui/app36/HelloController.java | 36 +++++++++++ .../ui/app36/SpringDocApp36Test.java | 49 +++++++++++++++ .../springdoc/ui/app37/HelloController.java | 36 +++++++++++ .../ui/app37/SpringDocApp37Test.java | 53 ++++++++++++++++ .../springdoc/ui/app38/HelloController.java | 36 +++++++++++ .../ui/app38/SpringDocApp38Test.java | 48 ++++++++++++++ 11 files changed, 461 insertions(+), 2 deletions(-) create mode 100644 springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app35/HelloController.java create mode 100644 springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app35/SpringDocApp35Test.java create mode 100644 springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app36/HelloController.java create mode 100644 springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app36/SpringDocApp36Test.java create mode 100644 springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app36/HelloController.java create mode 100644 springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app36/SpringDocApp36Test.java create mode 100644 springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app37/HelloController.java create mode 100644 springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app37/SpringDocApp37Test.java create mode 100644 springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app38/HelloController.java create mode 100644 springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app38/SpringDocApp38Test.java diff --git a/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app34/SpringDocApp34Test.java b/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app34/SpringDocApp34Test.java index b478b2eb7..2d8f22a37 100644 --- a/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app34/SpringDocApp34Test.java +++ b/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app34/SpringDocApp34Test.java @@ -33,11 +33,14 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; -@TestPropertySource(properties = "springdoc.swagger-ui.disable-swagger-default-url=true") +@TestPropertySource(properties = { + "springdoc.swagger-ui.disable-swagger-default-url=true", + "springdoc.swagger-ui.path=/documentation/swagger-ui.html" +}) public class SpringDocApp34Test extends AbstractSpringDocTest { @Test - void transformed_index_with_oauth() throws Exception { + void testWebJarResourceTransformed() { EntityExchangeResult getResult = webTestClient.get().uri("/webjars/swagger-ui/swagger-initializer.js") .exchange() .expectStatus().isOk() diff --git a/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app35/HelloController.java b/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app35/HelloController.java new file mode 100644 index 000000000..ed6c4d9f9 --- /dev/null +++ b/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app35/HelloController.java @@ -0,0 +1,41 @@ +/* + * + * * + * * * + * * * * Copyright 2019-2020 the original author or authors. + * * * * + * * * * Licensed under the Apache License, Version 2.0 (the "License"); + * * * * you may not use this file except in compliance with the License. + * * * * You may obtain a copy of the License at + * * * * + * * * * https://www.apache.org/licenses/LICENSE-2.0 + * * * * + * * * * Unless required by applicable law or agreed to in writing, software + * * * * distributed under the License is distributed on an "AS IS" BASIS, + * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * * * See the License for the specific language governing permissions and + * * * * limitations under the License. + * * * + * * + * + * + */ + +package test.org.springdoc.ui.app35; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Size; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HelloController { + + @GetMapping(value = "/persons") + public void persons(@Valid @RequestParam @Size(min = 4, max = 6) String name) { + + } + +} diff --git a/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app35/SpringDocApp35Test.java b/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app35/SpringDocApp35Test.java new file mode 100644 index 000000000..b37e6a083 --- /dev/null +++ b/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app35/SpringDocApp35Test.java @@ -0,0 +1,63 @@ +/* + * + * * + * * * + * * * * Copyright 2019-2020 the original author or authors. + * * * * + * * * * Licensed under the Apache License, Version 2.0 (the "License"); + * * * * you may not use this file except in compliance with the License. + * * * * You may obtain a copy of the License at + * * * * + * * * * https://www.apache.org/licenses/LICENSE-2.0 + * * * * + * * * * Unless required by applicable law or agreed to in writing, software + * * * * distributed under the License is distributed on an "AS IS" BASIS, + * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * * * See the License for the specific language governing permissions and + * * * * limitations under the License. + * * * + * * + * + * + */ + +package test.org.springdoc.ui.app35; + +import org.junit.jupiter.api.Test; +import org.springframework.http.CacheControl; +import test.org.springdoc.ui.AbstractSpringDocTest; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.web.reactive.server.EntityExchangeResult; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@TestPropertySource(properties = { + "spring.webflux.webjars-path-pattern=/webjars-pref/**", + "springdoc.swagger-ui.disable-swagger-default-url=true", + "springdoc.swagger-ui.path=/documentation/swagger-ui.html" +}) +public class SpringDocApp35Test extends AbstractSpringDocTest { + + @Test + void testWebJarPrefix() { + webTestClient.get().uri("/webjars/swagger-ui/swagger-initializer.js") + .exchange() + .expectStatus().isNotFound(); + + EntityExchangeResult getResult = webTestClient.get().uri("/webjars-pref/swagger-ui/swagger-initializer.js") + .exchange() + .expectStatus().isOk() + .expectHeader().cacheControl(CacheControl.noStore()) + .expectBody().returnResult(); + + var responseContent = new String(getResult.getResponseBody()); + assertFalse(responseContent.contains("https://petstore.swagger.io/v2/swagger.json")); + assertTrue(responseContent.contains("/v3/api-docs")); + } + + @SpringBootApplication + static class SpringDocTestApp {} + +} diff --git a/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app36/HelloController.java b/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app36/HelloController.java new file mode 100644 index 000000000..1b34722d5 --- /dev/null +++ b/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app36/HelloController.java @@ -0,0 +1,41 @@ +/* + * + * * + * * * + * * * * Copyright 2019-2020 the original author or authors. + * * * * + * * * * Licensed under the Apache License, Version 2.0 (the "License"); + * * * * you may not use this file except in compliance with the License. + * * * * You may obtain a copy of the License at + * * * * + * * * * https://www.apache.org/licenses/LICENSE-2.0 + * * * * + * * * * Unless required by applicable law or agreed to in writing, software + * * * * distributed under the License is distributed on an "AS IS" BASIS, + * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * * * See the License for the specific language governing permissions and + * * * * limitations under the License. + * * * + * * + * + * + */ + +package test.org.springdoc.ui.app36; + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Size; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HelloController { + + @GetMapping(value = "/persons") + public void persons(@Valid @RequestParam @Size(min = 4, max = 6) String name) { + + } + +} diff --git a/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app36/SpringDocApp36Test.java b/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app36/SpringDocApp36Test.java new file mode 100644 index 000000000..c80dd9347 --- /dev/null +++ b/springdoc-openapi-starter-webflux-ui/src/test/java/test/org/springdoc/ui/app36/SpringDocApp36Test.java @@ -0,0 +1,53 @@ +/* + * + * * + * * * + * * * * Copyright 2019-2020 the original author or authors. + * * * * + * * * * Licensed under the Apache License, Version 2.0 (the "License"); + * * * * you may not use this file except in compliance with the License. + * * * * You may obtain a copy of the License at + * * * * + * * * * https://www.apache.org/licenses/LICENSE-2.0 + * * * * + * * * * Unless required by applicable law or agreed to in writing, software + * * * * distributed under the License is distributed on an "AS IS" BASIS, + * * * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * * * See the License for the specific language governing permissions and + * * * * limitations under the License. + * * * + * * + * + * + */ + +package test.org.springdoc.ui.app36; + +import org.junit.jupiter.api.Test; +import test.org.springdoc.ui.AbstractSpringDocTest; + +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.test.context.TestPropertySource; + +@TestPropertySource(properties = { + "spring.webflux.webjars-path-pattern=/webjars-pref/**", + "spring.web.resources.add-mappings=false", + "springdoc.swagger-ui.path=/documentation/swagger-ui.html" +}) +public class SpringDocApp36Test extends AbstractSpringDocTest { + + @Test + void testWebJarMappingDisabled() { + webTestClient.get().uri("/webjars/swagger-ui/swagger-initializer.js") + .exchange() + .expectStatus().isNotFound(); + + webTestClient.get().uri("/webjars-pref/swagger-ui/swagger-initializer.js") + .exchange() + .expectStatus().isNotFound(); + } + + @SpringBootApplication + static class SpringDocTestApp {} + +} diff --git a/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app36/HelloController.java b/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app36/HelloController.java new file mode 100644 index 000000000..5129f7062 --- /dev/null +++ b/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app36/HelloController.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2019-2020 the original author or authors. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * https://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package test.org.springdoc.ui.app36; + + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Size; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HelloController { + + @GetMapping(value = "/persons") + public void persons(@Valid @RequestParam @Size(min = 4, max = 6) String name) { + + } + +} diff --git a/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app36/SpringDocApp36Test.java b/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app36/SpringDocApp36Test.java new file mode 100644 index 000000000..7902fe474 --- /dev/null +++ b/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app36/SpringDocApp36Test.java @@ -0,0 +1,49 @@ +/* + * + * * Copyright 2019-2020 the original author or authors. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * https://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package test.org.springdoc.ui.app36; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.test.context.TestPropertySource; +import test.org.springdoc.ui.AbstractSpringDocTest; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@TestPropertySource(properties = { + "springdoc.swagger-ui.disable-swagger-default-url=true", + "springdoc.swagger-ui.path=/documentation/swagger-ui.html" +}) +public class SpringDocApp36Test extends AbstractSpringDocTest { + + @Test + void testWebJarResourceTransformed() throws Exception { + mockMvc.perform(get("/webjars/swagger-ui/swagger-initializer.js")) + .andExpect(status().isOk()) + .andExpect(content().string(not(containsString("https://petstore.swagger.io/v2/swagger.json")))) + .andExpect(content().string(containsString("/v3/api-docs"))); + } + + @SpringBootApplication + static class SpringDocTestApp {} + +} diff --git a/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app37/HelloController.java b/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app37/HelloController.java new file mode 100644 index 000000000..62daa6a88 --- /dev/null +++ b/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app37/HelloController.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2019-2020 the original author or authors. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * https://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package test.org.springdoc.ui.app37; + + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Size; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HelloController { + + @GetMapping(value = "/persons") + public void persons(@Valid @RequestParam @Size(min = 4, max = 6) String name) { + + } + +} diff --git a/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app37/SpringDocApp37Test.java b/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app37/SpringDocApp37Test.java new file mode 100644 index 000000000..e3859e58e --- /dev/null +++ b/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app37/SpringDocApp37Test.java @@ -0,0 +1,53 @@ +/* + * + * * Copyright 2019-2020 the original author or authors. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * https://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package test.org.springdoc.ui.app37; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.test.context.TestPropertySource; +import test.org.springdoc.ui.AbstractSpringDocTest; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@TestPropertySource(properties = { + "spring.mvc.webjars-path-pattern=/webjars-pref/**", + "springdoc.swagger-ui.disable-swagger-default-url=true", + "springdoc.swagger-ui.path=/documentation/swagger-ui.html" +}) +public class SpringDocApp37Test extends AbstractSpringDocTest { + + @Test + void testWebJarPrefix() throws Exception { + mockMvc.perform(get("/webjars/swagger-ui/swagger-initializer.js")) + .andExpect(status().isNotFound()); + + mockMvc.perform(get("/webjars-pref/swagger-ui/swagger-initializer.js")) + .andExpect(status().isOk()) + .andExpect(content().string(not(containsString("https://petstore.swagger.io/v2/swagger.json")))) + .andExpect(content().string(containsString("/v3/api-docs"))); + } + + @SpringBootApplication + static class SpringDocTestApp {} + +} diff --git a/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app38/HelloController.java b/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app38/HelloController.java new file mode 100644 index 000000000..603b7928e --- /dev/null +++ b/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app38/HelloController.java @@ -0,0 +1,36 @@ +/* + * + * * Copyright 2019-2020 the original author or authors. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * https://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package test.org.springdoc.ui.app38; + + +import jakarta.validation.Valid; +import jakarta.validation.constraints.Size; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HelloController { + + @GetMapping(value = "/persons") + public void persons(@Valid @RequestParam @Size(min = 4, max = 6) String name) { + + } + +} diff --git a/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app38/SpringDocApp38Test.java b/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app38/SpringDocApp38Test.java new file mode 100644 index 000000000..aee3b80fd --- /dev/null +++ b/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app38/SpringDocApp38Test.java @@ -0,0 +1,48 @@ +/* + * + * * Copyright 2019-2020 the original author or authors. + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * https://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package test.org.springdoc.ui.app38; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.test.context.TestPropertySource; +import test.org.springdoc.ui.AbstractSpringDocTest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@TestPropertySource(properties = { + "spring.mvc.webjars-path-pattern=/webjars-pref/**", + "spring.web.resources.add-mappings=false", + "springdoc.swagger-ui.path=/documentation/swagger-ui.html" +}) +public class SpringDocApp38Test extends AbstractSpringDocTest { + + @Test + void testWebJarMappingDisabled() throws Exception { + mockMvc.perform(get("/webjars/swagger-ui/swagger-initializer.js")) + .andExpect(status().isNotFound()); + + mockMvc.perform(get("/webjars-pref/swagger-ui/swagger-initializer.js")) + .andExpect(status().isNotFound()); + } + + @SpringBootApplication + static class SpringDocTestApp {} + +} From 99620817edc94a4603ceea23e31f7e3640a00a77 Mon Sep 17 00:00:00 2001 From: James Missen <88438228+jamesmissen@users.noreply.github.com> Date: Sun, 23 Nov 2025 01:17:11 +1300 Subject: [PATCH 4/6] Use Swagger welcome to get UI root path for resource handlers --- .../springdoc/webflux/ui/SwaggerConfig.java | 9 ++- .../webflux/ui/SwaggerConfigResource.java | 8 +-- .../ui/SwaggerIndexPageTransformer.java | 4 +- .../webflux/ui/SwaggerWebFluxConfigurer.java | 36 +++++------- .../webflux/ui/SwaggerWelcomeActuator.java | 16 +++--- .../webflux/ui/SwaggerWelcomeCommon.java | 56 +++++++++---------- .../webflux/ui/SwaggerWelcomeWebFlux.java | 10 ++-- .../springdoc/webmvc/ui/SwaggerConfig.java | 9 ++- .../webmvc/ui/SwaggerWebMvcConfigurer.java | 37 +++++------- .../webmvc/ui/SwaggerWelcomeCommon.java | 28 +++++++++- .../webmvc/ui/SwaggerWelcomeWebMvc.java | 17 ++---- ...ltipleUrlsSeveralParallelRequestsTest.java | 2 +- 12 files changed, 114 insertions(+), 118 deletions(-) diff --git a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerConfig.java b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerConfig.java index 4dfccda79..b8260dd0f 100644 --- a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerConfig.java +++ b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerConfig.java @@ -33,7 +33,6 @@ import org.springdoc.core.properties.SpringDocConfigProperties; import org.springdoc.core.properties.SwaggerUiConfigProperties; import org.springdoc.core.properties.SwaggerUiOAuthProperties; -import org.springdoc.core.providers.ActuatorProvider; import org.springdoc.core.providers.ObjectMapperProvider; import org.springdoc.core.providers.SpringWebProvider; import org.springdoc.webflux.core.providers.SpringWebFluxProvider; @@ -125,8 +124,8 @@ SwaggerUiHome swaggerUiHome(Optional optionalWebFluxPropertie * @param springWebProperties the spring web config * @param springWebFluxProperties the spring webflux config * @param swaggerIndexTransformer the swagger index transformer - * @param actuatorProvider the actuator provider * @param swaggerResourceResolver the swagger resource resolver + * @param swaggerWelcomeCommon the swagger welcome common * @return the swagger web flux configurer */ @Bean @@ -134,9 +133,9 @@ SwaggerUiHome swaggerUiHome(Optional optionalWebFluxPropertie @Lazy(false) SwaggerWebFluxConfigurer swaggerWebFluxConfigurer(SwaggerUiConfigProperties swaggerUiConfigProperties, WebProperties springWebProperties, WebFluxProperties springWebFluxProperties, - SwaggerIndexTransformer swaggerIndexTransformer, Optional actuatorProvider, - SwaggerResourceResolver swaggerResourceResolver) { - return new SwaggerWebFluxConfigurer(swaggerUiConfigProperties, springWebProperties, springWebFluxProperties, swaggerIndexTransformer, actuatorProvider, swaggerResourceResolver); + SwaggerIndexTransformer swaggerIndexTransformer, SwaggerResourceResolver swaggerResourceResolver, + SwaggerWelcomeCommon swaggerWelcomeCommon) { + return new SwaggerWebFluxConfigurer(swaggerUiConfigProperties, springWebProperties, springWebFluxProperties, swaggerIndexTransformer, swaggerResourceResolver, swaggerWelcomeCommon); } /** diff --git a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerConfigResource.java b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerConfigResource.java index cf803ddc5..560de709f 100644 --- a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerConfigResource.java +++ b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerConfigResource.java @@ -30,9 +30,9 @@ import io.swagger.v3.oas.annotations.Operation; import org.springframework.http.MediaType; -import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ServerWebExchange; import static org.springdoc.core.utils.Constants.SWAGGER_CONFIG_URL; @@ -61,13 +61,13 @@ public SwaggerConfigResource(SwaggerWelcomeCommon swaggerWelcomeCommon) { /** * Gets swagger ui config. * - * @param request the request + * @param exchange the exchange * @return the swagger ui config */ @Operation(hidden = true) @GetMapping(value = SWAGGER_CONFIG_URL, produces = MediaType.APPLICATION_JSON_VALUE) - public Map getSwaggerUiConfig(ServerHttpRequest request) { - return swaggerWelcomeCommon.getSwaggerUiConfig(request); + public Map getSwaggerUiConfig(ServerWebExchange exchange) { + return swaggerWelcomeCommon.getSwaggerUiConfig(exchange); } } diff --git a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerIndexPageTransformer.java b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerIndexPageTransformer.java index d68eb838d..190d2432d 100644 --- a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerIndexPageTransformer.java +++ b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerIndexPageTransformer.java @@ -71,9 +71,9 @@ public SwaggerIndexPageTransformer(SwaggerUiConfigProperties swaggerUiConfig, Sw } @Override - public Mono transform(ServerWebExchange serverWebExchange, Resource resource, ResourceTransformerChain resourceTransformerChain) { + public Mono transform(ServerWebExchange exchange, Resource resource, ResourceTransformerChain resourceTransformerChain) { SwaggerUiConfigParameters swaggerUiConfigParameters = new SwaggerUiConfigParameters(swaggerUiConfig); - swaggerWelcomeCommon.buildFromCurrentContextPath(swaggerUiConfigParameters, serverWebExchange.getRequest()); + swaggerWelcomeCommon.buildFromCurrentContextPath(swaggerUiConfigParameters, exchange); final AntPathMatcher antPathMatcher = new AntPathMatcher(); try { diff --git a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWebFluxConfigurer.java b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWebFluxConfigurer.java index 827254c04..db07ca427 100644 --- a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWebFluxConfigurer.java +++ b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWebFluxConfigurer.java @@ -26,9 +26,9 @@ package org.springdoc.webflux.ui; -import java.util.Optional; +import org.springdoc.core.properties.SwaggerUiConfigParameters; import org.springdoc.core.properties.SwaggerUiConfigProperties; -import org.springdoc.core.providers.ActuatorProvider; + import org.springframework.boot.autoconfigure.web.WebProperties; import org.springframework.boot.webflux.autoconfigure.WebFluxProperties; import org.springframework.http.CacheControl; @@ -56,11 +56,6 @@ public class SwaggerWebFluxConfigurer implements WebFluxConfigurer { */ private final SwaggerIndexTransformer swaggerIndexTransformer; - /** - * The Actuator provider. - */ - private final Optional actuatorProvider; - /** * The Swagger resource resolver. */ @@ -81,6 +76,11 @@ public class SwaggerWebFluxConfigurer implements WebFluxConfigurer { */ private final WebFluxProperties springWebFluxProperties; + /** + * The Swagger welcome common. + */ + private final SwaggerWelcomeCommon swaggerWelcomeCommon; + private final PathPatternParser parser = new PathPatternParser(); /** @@ -90,19 +90,19 @@ public class SwaggerWebFluxConfigurer implements WebFluxConfigurer { * @param springWebProperties the spring web config * @param springWebFluxProperties the spring webflux config * @param swaggerIndexTransformer the swagger index transformer - * @param actuatorProvider the actuator provider * @param swaggerResourceResolver the swagger resource resolver + * @param swaggerWelcomeCommon the swagger welcome common */ public SwaggerWebFluxConfigurer(SwaggerUiConfigProperties swaggerUiConfigProperties, WebProperties springWebProperties, WebFluxProperties springWebFluxProperties, - SwaggerIndexTransformer swaggerIndexTransformer, Optional actuatorProvider, - SwaggerResourceResolver swaggerResourceResolver) { + SwaggerIndexTransformer swaggerIndexTransformer, SwaggerResourceResolver swaggerResourceResolver, + SwaggerWelcomeCommon swaggerWelcomeCommon) { this.swaggerIndexTransformer = swaggerIndexTransformer; - this.actuatorProvider = actuatorProvider; this.swaggerResourceResolver = swaggerResourceResolver; this.swaggerUiConfigProperties = swaggerUiConfigProperties; this.springWebProperties = springWebProperties; this.springWebFluxProperties = springWebFluxProperties; + this.swaggerWelcomeCommon = swaggerWelcomeCommon; } @Override @@ -149,18 +149,10 @@ protected void addSwaggerUiResourceHandler(ResourceHandlerRegistry registry, Str * @return the Swagger UI root path. */ protected String getUiRootPath() { - StringBuilder uiRootPath = new StringBuilder(); - - if (actuatorProvider.isPresent() && actuatorProvider.get().isUseManagementPort()) { - uiRootPath.append(actuatorProvider.get().getBasePath()); - } - - String swaggerUiPath = swaggerUiConfigProperties.getPath(); - if (swaggerUiPath.contains(DEFAULT_PATH_SEPARATOR)) { - uiRootPath.append(swaggerUiPath, 0, swaggerUiPath.lastIndexOf(DEFAULT_PATH_SEPARATOR)); - } + SwaggerUiConfigParameters swaggerUiConfigParameters = new SwaggerUiConfigParameters(swaggerUiConfigProperties); + swaggerWelcomeCommon.calculateUiRootPath(swaggerUiConfigParameters); - return uiRootPath.toString(); + return swaggerUiConfigParameters.getUiRootPath(); } /** diff --git a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeActuator.java b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeActuator.java index e8b2a2c16..7f4b394b2 100644 --- a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeActuator.java +++ b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeActuator.java @@ -37,10 +37,9 @@ import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties; import org.springframework.boot.actuate.endpoint.web.annotation.ControllerEndpoint; import org.springframework.http.MediaType; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.server.ServerWebExchange; import static org.springdoc.core.utils.Constants.DEFAULT_API_DOCS_ACTUATOR_URL; import static org.springdoc.core.utils.Constants.DEFAULT_SWAGGER_UI_ACTUATOR_PATH; @@ -82,29 +81,28 @@ public SwaggerWelcomeActuator(SwaggerUiConfigProperties swaggerUiConfig /** * Redirect to ui mono. * - * @param request the request - * @param response the response + * @param exchange the exchange * @return the mono */ @Operation(hidden = true) @GetMapping(DEFAULT_PATH_SEPARATOR) @Override - public Mono redirectToUi(ServerHttpRequest request, ServerHttpResponse response) { - return super.redirectToUi(request, response); + public Mono redirectToUi(ServerWebExchange exchange) { + return super.redirectToUi(exchange); } /** * Openapi yaml map. * - * @param request the request + * @param exchange the exchange * @return the map */ @Operation(hidden = true) @GetMapping(value = SWAGGER_CONFIG_ACTUATOR_URL, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody @Override - public Map getSwaggerUiConfig(ServerHttpRequest request) { - return super.getSwaggerUiConfig(request); + public Map getSwaggerUiConfig(ServerWebExchange exchange) { + return super.getSwaggerUiConfig(exchange); } @Override diff --git a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeCommon.java b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeCommon.java index 4cf80ccfd..a53c42408 100644 --- a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeCommon.java +++ b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeCommon.java @@ -34,12 +34,12 @@ import org.springdoc.core.properties.SwaggerUiConfigParameters; import org.springdoc.core.properties.SwaggerUiConfigProperties; import org.springdoc.ui.AbstractSwaggerWelcome; +import org.springframework.http.HttpHeaders; +import org.springframework.web.util.ForwardedHeaderUtils; import reactor.core.publisher.Mono; import org.springframework.http.HttpStatus; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; -import org.springframework.web.util.ForwardedHeaderUtils; +import org.springframework.web.server.ServerWebExchange; import org.springframework.web.util.UriComponentsBuilder; /** @@ -63,22 +63,21 @@ protected SwaggerWelcomeCommon(SwaggerUiConfigProperties swaggerUiConfig, Spring /** * Redirect to ui mono. * - * @param request the request - * @param response the response + * @param exchange the exchange * @return the mono */ - protected Mono redirectToUi(ServerHttpRequest request, ServerHttpResponse response) { + protected Mono redirectToUi(ServerWebExchange exchange) { SwaggerUiConfigParameters swaggerUiConfigParameters = new SwaggerUiConfigParameters(swaggerUiConfig); - buildFromCurrentContextPath(swaggerUiConfigParameters, request); + buildFromCurrentContextPath(swaggerUiConfigParameters, exchange); String sbUrl = swaggerUiConfigParameters.getContextPath() + swaggerUiConfigParameters.getUiRootPath() + getSwaggerUiUrl(); UriComponentsBuilder uriBuilder = getUriComponentsBuilder(swaggerUiConfigParameters, sbUrl); // forward all queryParams from original request - request.getQueryParams().forEach(uriBuilder::queryParam); + exchange.getRequest().getQueryParams().forEach(uriBuilder::queryParam); - response.setStatusCode(HttpStatus.FOUND); - response.getHeaders().setLocation(URI.create(uriBuilder.build().encode().toString())); - return response.setComplete(); + exchange.getResponse().setStatusCode(HttpStatus.FOUND); + exchange.getResponse().getHeaders().setLocation(URI.create(uriBuilder.build().encode().toString())); + return exchange.getResponse().setComplete(); } @Override @@ -90,15 +89,18 @@ protected void calculateOauth2RedirectUrl(SwaggerUiConfigParameters swaggerUiCon } } + @Override + protected abstract void calculateUiRootPath(SwaggerUiConfigParameters swaggerUiConfigParameters, StringBuilder... sbUrls); + /** * Gets swagger ui config. * - * @param request the request + * @param exchange the exchange * @return the swagger ui config */ - protected Map getSwaggerUiConfig(ServerHttpRequest request) { + protected Map getSwaggerUiConfig(ServerWebExchange exchange) { SwaggerUiConfigParameters swaggerUiConfigParameters = new SwaggerUiConfigParameters(swaggerUiConfig); - this.buildFromCurrentContextPath(swaggerUiConfigParameters, request); + this.buildFromCurrentContextPath(swaggerUiConfigParameters, exchange); return swaggerUiConfigParameters.getConfigParameters(); } @@ -106,22 +108,20 @@ protected Map getSwaggerUiConfig(ServerHttpRequest request) { * From current context path string. * * @param swaggerUiConfigParameters the swagger ui config parameters - * @param request the request - * @return the string + * @param exchange the exchange */ - void buildFromCurrentContextPath(SwaggerUiConfigParameters swaggerUiConfigParameters, ServerHttpRequest request) { + void buildFromCurrentContextPath(SwaggerUiConfigParameters swaggerUiConfigParameters, ServerWebExchange exchange) { super.init(swaggerUiConfigParameters); - swaggerUiConfigParameters.setContextPath(request.getPath().contextPath().value()); - String url = ForwardedHeaderUtils.adaptFromForwardedHeaders(request.getURI(), request.getHeaders()).toUriString(); - String target = UriComponentsBuilder.fromPath(request.getPath().contextPath().value()).toUriString(); - int endIndex = url.indexOf(target) + target.length(); - if (endIndex > 0) { - url = url.substring(0, endIndex); - } - else { - url = url.replace(request.getPath().toString(), ""); - } - buildConfigUrl(swaggerUiConfigParameters, UriComponentsBuilder.fromUriString(url)); + + String contextPath = exchange.getRequest().getPath().contextPath().value(); + swaggerUiConfigParameters.setContextPath(contextPath); + + URI uri = exchange.getRequest().getURI(); + HttpHeaders headers = exchange.getRequest().getHeaders(); + + UriComponentsBuilder uriComponentsBuilder = ForwardedHeaderUtils.adaptFromForwardedHeaders(uri, headers) + .replacePath(contextPath).replaceQuery(null).fragment(null); + buildConfigUrl(swaggerUiConfigParameters, uriComponentsBuilder); } } diff --git a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeWebFlux.java b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeWebFlux.java index 12e630fd9..84ab55436 100644 --- a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeWebFlux.java +++ b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeWebFlux.java @@ -33,10 +33,9 @@ import org.springdoc.core.providers.SpringWebProvider; import reactor.core.publisher.Mono; -import org.springframework.http.server.reactive.ServerHttpRequest; -import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.server.ServerWebExchange; import static org.springdoc.core.utils.Constants.SWAGGER_CONFIG_FILE; import static org.springdoc.core.utils.Constants.SWAGGER_UI_PATH; @@ -71,15 +70,14 @@ public SwaggerWelcomeWebFlux(SwaggerUiConfigProperties swaggerUiConfig, SpringDo /** * Redirect to ui mono. * - * @param request the request - * @param response the response + * @param exchange the exchange * @return the mono */ @Operation(hidden = true) @GetMapping(SWAGGER_UI_PATH) @Override - public Mono redirectToUi(ServerHttpRequest request, ServerHttpResponse response) { - return super.redirectToUi(request, response); + public Mono redirectToUi(ServerWebExchange exchange) { + return super.redirectToUi(exchange); } @Override diff --git a/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerConfig.java b/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerConfig.java index 1f5b74781..0182c1f86 100644 --- a/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerConfig.java +++ b/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerConfig.java @@ -33,7 +33,6 @@ import org.springdoc.core.properties.SpringDocConfigProperties; import org.springdoc.core.properties.SwaggerUiConfigProperties; import org.springdoc.core.properties.SwaggerUiOAuthProperties; -import org.springdoc.core.providers.ActuatorProvider; import org.springdoc.core.providers.ObjectMapperProvider; import org.springdoc.core.providers.SpringWebProvider; import org.springdoc.webmvc.core.providers.SpringWebMvcProvider; @@ -153,8 +152,8 @@ SwaggerIndexTransformer indexPageTransformer(SwaggerUiConfigProperties swaggerUi * @param springWebProperties the spring web config * @param springWebMvcProperties the spring mvc config * @param swaggerIndexTransformer the swagger index transformer - * @param actuatorProvider the actuator provider * @param swaggerResourceResolver the swagger resource resolver + * @param swaggerWelcomeCommon the swagger welcome common * @return the swagger web mvc configurer */ @Bean @@ -162,9 +161,9 @@ SwaggerIndexTransformer indexPageTransformer(SwaggerUiConfigProperties swaggerUi @Lazy(false) SwaggerWebMvcConfigurer swaggerWebMvcConfigurer(SwaggerUiConfigProperties swaggerUiConfigProperties, WebProperties springWebProperties, WebMvcProperties springWebMvcProperties, - SwaggerIndexTransformer swaggerIndexTransformer, Optional actuatorProvider, - SwaggerResourceResolver swaggerResourceResolver) { - return new SwaggerWebMvcConfigurer(swaggerUiConfigProperties, springWebProperties, springWebMvcProperties, swaggerIndexTransformer, actuatorProvider, swaggerResourceResolver); + SwaggerIndexTransformer swaggerIndexTransformer, SwaggerResourceResolver swaggerResourceResolver, + SwaggerWelcomeCommon swaggerWelcomeCommon) { + return new SwaggerWebMvcConfigurer(swaggerUiConfigProperties, springWebProperties, springWebMvcProperties, swaggerIndexTransformer, swaggerResourceResolver, swaggerWelcomeCommon); } /** diff --git a/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWebMvcConfigurer.java b/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWebMvcConfigurer.java index 34fcda8b2..621779138 100644 --- a/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWebMvcConfigurer.java +++ b/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWebMvcConfigurer.java @@ -27,9 +27,10 @@ package org.springdoc.webmvc.ui; import java.util.List; -import java.util.Optional; + +import org.springdoc.core.properties.SwaggerUiConfigParameters; import org.springdoc.core.properties.SwaggerUiConfigProperties; -import org.springdoc.core.providers.ActuatorProvider; + import org.springframework.boot.autoconfigure.web.WebProperties; import org.springframework.boot.webmvc.autoconfigure.WebMvcProperties; import org.springframework.format.FormatterRegistry; @@ -74,11 +75,6 @@ public class SwaggerWebMvcConfigurer implements WebMvcConfigurer { */ private final SwaggerIndexTransformer swaggerIndexTransformer; - /** - * The Actuator provider. - */ - private final Optional actuatorProvider; - /** * The Swagger resource resolver. */ @@ -99,6 +95,11 @@ public class SwaggerWebMvcConfigurer implements WebMvcConfigurer { */ private final WebMvcProperties springWebMvcProperties; + /** + * The Swagger welcome common. + */ + private final SwaggerWelcomeCommon swaggerWelcomeCommon; + private final PathPatternParser parser = new PathPatternParser(); /** @@ -108,19 +109,19 @@ public class SwaggerWebMvcConfigurer implements WebMvcConfigurer { * @param springWebProperties the spring web config * @param springWebMvcProperties the spring mvc config * @param swaggerIndexTransformer the swagger index transformer - * @param actuatorProvider the actuator provider * @param swaggerResourceResolver the swagger resource resolver + * @param swaggerWelcomeCommon the swagger welcome common */ public SwaggerWebMvcConfigurer(SwaggerUiConfigProperties swaggerUiConfigProperties, WebProperties springWebProperties, WebMvcProperties springWebMvcProperties, - SwaggerIndexTransformer swaggerIndexTransformer, Optional actuatorProvider, - SwaggerResourceResolver swaggerResourceResolver) { + SwaggerIndexTransformer swaggerIndexTransformer, SwaggerResourceResolver swaggerResourceResolver, + SwaggerWelcomeCommon swaggerWelcomeCommon) { this.swaggerIndexTransformer = swaggerIndexTransformer; - this.actuatorProvider = actuatorProvider; this.swaggerResourceResolver = swaggerResourceResolver; this.swaggerUiConfigProperties = swaggerUiConfigProperties; this.springWebProperties = springWebProperties; this.springWebMvcProperties = springWebMvcProperties; + this.swaggerWelcomeCommon = swaggerWelcomeCommon; } @Override @@ -167,18 +168,10 @@ protected void addSwaggerUiResourceHandler(ResourceHandlerRegistry registry, Str * @return the Swagger UI root path. */ protected String getUiRootPath() { - StringBuilder uiRootPath = new StringBuilder(); - - if (actuatorProvider.isPresent() && actuatorProvider.get().isUseManagementPort()) { - uiRootPath.append(actuatorProvider.get().getBasePath()); - } - - String swaggerUiPath = swaggerUiConfigProperties.getPath(); - if (swaggerUiPath.contains(DEFAULT_PATH_SEPARATOR)) { - uiRootPath.append(swaggerUiPath, 0, swaggerUiPath.lastIndexOf(DEFAULT_PATH_SEPARATOR)); - } + SwaggerUiConfigParameters swaggerUiConfigParameters = new SwaggerUiConfigParameters(swaggerUiConfigProperties); + swaggerWelcomeCommon.calculateUiRootPath(swaggerUiConfigParameters); - return uiRootPath.toString(); + return swaggerUiConfigParameters.getUiRootPath(); } /** diff --git a/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWelcomeCommon.java b/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWelcomeCommon.java index 7fa79368a..5f47f2f4a 100644 --- a/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWelcomeCommon.java +++ b/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWelcomeCommon.java @@ -26,6 +26,8 @@ package org.springdoc.webmvc.ui; +import java.net.URI; +import java.util.Collections; import java.util.Map; import jakarta.servlet.http.HttpServletRequest; @@ -35,10 +37,12 @@ import org.springdoc.core.properties.SwaggerUiConfigProperties; import org.springdoc.ui.AbstractSwaggerWelcome; +import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import org.springframework.web.util.ForwardedHeaderUtils; import org.springframework.web.util.UriComponentsBuilder; +import org.springframework.web.util.UrlPathHelper; /** * The type Swagger welcome common. @@ -46,6 +50,9 @@ * @author bnasslashen */ public abstract class SwaggerWelcomeCommon extends AbstractSwaggerWelcome { + + private final UrlPathHelper urlPathHelper = new UrlPathHelper(); + /** * Instantiates a new Abstract swagger welcome. * @@ -96,6 +103,9 @@ protected void calculateOauth2RedirectUrl(SwaggerUiConfigParameters swaggerUiCon .path(getOauth2RedirectUrl()).build().toString()); } + @Override + protected abstract void calculateUiRootPath(SwaggerUiConfigParameters swaggerUiConfigParameters, StringBuilder... sbUrls); + /** * From current context path. * @@ -104,8 +114,20 @@ protected void calculateOauth2RedirectUrl(SwaggerUiConfigParameters swaggerUiCon */ void buildFromCurrentContextPath(SwaggerUiConfigParameters swaggerUiConfigParameters, HttpServletRequest request) { super.init(swaggerUiConfigParameters); - swaggerUiConfigParameters.setContextPath(request.getContextPath()); - buildConfigUrl(swaggerUiConfigParameters, ServletUriComponentsBuilder.fromCurrentContextPath()); + + String pathWithinServletMapping = urlPathHelper.getPathWithinServletMapping(request); + String contextPath = request.getContextPath() + (!StringUtils.isBlank(pathWithinServletMapping) ? request.getServletPath() : ""); + swaggerUiConfigParameters.setContextPath(contextPath); + + URI uri = URI.create(request.getRequestURL().toString()); + HttpHeaders headers = new HttpHeaders(); + for (String name : Collections.list(request.getHeaderNames())) { + headers.addAll(name, Collections.list(request.getHeaders(name))); + } + + UriComponentsBuilder uriComponentsBuilder = ForwardedHeaderUtils.adaptFromForwardedHeaders(uri, headers) + .replacePath(contextPath).replaceQuery(null).fragment(null); + buildConfigUrl(swaggerUiConfigParameters, uriComponentsBuilder); } } diff --git a/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWelcomeWebMvc.java b/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWelcomeWebMvc.java index a6be3f41e..236762f05 100644 --- a/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWelcomeWebMvc.java +++ b/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWelcomeWebMvc.java @@ -32,7 +32,6 @@ import org.springdoc.core.properties.SwaggerUiConfigParameters; import org.springdoc.core.properties.SwaggerUiConfigProperties; import org.springdoc.core.providers.SpringWebProvider; -import org.springdoc.core.utils.SpringDocUtils; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; @@ -86,18 +85,9 @@ public ResponseEntity redirectToUi(HttpServletRequest request) { @Override protected void calculateUiRootPath(SwaggerUiConfigParameters swaggerUiConfigParameters, StringBuilder... sbUrls) { StringBuilder sbUrl = new StringBuilder(); - if (SpringDocUtils.isValidPath(mvcServletPath)) - sbUrl.append(mvcServletPath); calculateUiRootCommon(swaggerUiConfigParameters, sbUrl, sbUrls); } - @Override - protected String buildUrl(String contextPath, final String docsUrl) { - if (SpringDocUtils.isValidPath(mvcServletPath)) - contextPath += mvcServletPath; - return super.buildUrl(contextPath, docsUrl); - } - @Override protected void buildApiDocUrl(SwaggerUiConfigParameters swaggerUiConfigParameters) { swaggerUiConfigParameters.setApiDocsUrl(buildUrlWithContextPath(swaggerUiConfigParameters, springDocConfigProperties.getApiDocs().getPath())); @@ -107,7 +97,12 @@ protected void buildApiDocUrl(SwaggerUiConfigParameters swaggerUiConfigParameter protected String buildUrlWithContextPath(SwaggerUiConfigParameters swaggerUiConfigParameters, String swaggerUiUrl) { if (swaggerUiConfigParameters.getPathPrefix() == null) swaggerUiConfigParameters.setPathPrefix(springWebProvider.findPathPrefix(springDocConfigProperties)); - return buildUrl(swaggerUiConfigParameters.getContextPath() + swaggerUiConfigParameters.getPathPrefix(), swaggerUiUrl); + if (swaggerUiUrl.startsWith(swaggerUiConfigParameters.getPathPrefix())) { + return buildUrl(swaggerUiConfigParameters.getContextPath(), swaggerUiUrl); + } + else { + return buildUrl(swaggerUiConfigParameters.getContextPath() + swaggerUiConfigParameters.getPathPrefix(), swaggerUiUrl); + } } @Override diff --git a/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app8/SpringDocApp8MultipleUrlsSeveralParallelRequestsTest.java b/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app8/SpringDocApp8MultipleUrlsSeveralParallelRequestsTest.java index 77919360c..91142151f 100644 --- a/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app8/SpringDocApp8MultipleUrlsSeveralParallelRequestsTest.java +++ b/springdoc-openapi-starter-webmvc-ui/src/test/java/test/org/springdoc/ui/app8/SpringDocApp8MultipleUrlsSeveralParallelRequestsTest.java @@ -63,7 +63,7 @@ void swagger_config_for_multiple_groups_and_many_parallel_requests() { assertDoesNotThrow(() -> { allOf(Stream.generate(() -> runAsync(() -> { try { - mockMvc.perform(get("/v3/api-docs/swagger-config")) + mockMvc.perform(get("/servlet-path/v3/api-docs/swagger-config").servletPath("/servlet-path")) .andExpect(status().isOk()) .andExpect(jsonPath("configUrl", equalTo("/servlet-path/v3/api-docs/swagger-config"))) .andExpect(jsonPath("url").doesNotExist()) From cc1a4e58c06d9555a352ded95dbea01f194dae1b Mon Sep 17 00:00:00 2001 From: James Missen <88438228+jamesmissen@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:19:43 +1300 Subject: [PATCH 5/6] Abstract shared Swagger configurer logic --- .../org/springdoc/core/utils/Constants.java | 5 + .../ui/AbstractSwaggerConfigurer.java | 196 ++++++++++++++++++ .../webflux/ui/SwaggerWebFluxConfigurer.java | 125 ++++++----- .../webmvc/ui/SwaggerWebMvcConfigurer.java | 124 +++++------ 4 files changed, 316 insertions(+), 134 deletions(-) create mode 100644 springdoc-openapi-starter-common/src/main/java/org/springdoc/ui/AbstractSwaggerConfigurer.java diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/Constants.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/Constants.java index 031d16f10..9d0bb0db5 100644 --- a/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/Constants.java +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/core/utils/Constants.java @@ -376,6 +376,11 @@ public final class Constants { */ public static final String SWAGGER_INITIALIZER_PATTERN = "/*" + SWAGGER_INITIALIZER_JS; + /** + * The constant SWAGGER_RESOURCE_CACHE_NAME. + */ + public static final String SWAGGER_RESOURCE_CACHE_NAME = "swagger-resource-chain-cache"; + /** * The constant HEALTH_PATTERN. */ diff --git a/springdoc-openapi-starter-common/src/main/java/org/springdoc/ui/AbstractSwaggerConfigurer.java b/springdoc-openapi-starter-common/src/main/java/org/springdoc/ui/AbstractSwaggerConfigurer.java new file mode 100644 index 000000000..583dd2ffb --- /dev/null +++ b/springdoc-openapi-starter-common/src/main/java/org/springdoc/ui/AbstractSwaggerConfigurer.java @@ -0,0 +1,196 @@ +package org.springdoc.ui; + +import org.springdoc.core.properties.SwaggerUiConfigProperties; +import org.springframework.boot.autoconfigure.web.WebProperties; +import org.springframework.web.util.pattern.PathPattern; +import org.springframework.web.util.pattern.PathPatternParser; +import org.springframework.web.util.pattern.PatternParseException; + +import java.util.Arrays; + +import static org.springdoc.core.utils.Constants.ALL_PATTERN; +import static org.springdoc.core.utils.Constants.SWAGGER_INITIALIZER_PATTERN; +import static org.springdoc.core.utils.Constants.SWAGGER_UI_PREFIX; +import static org.springdoc.core.utils.Constants.SWAGGER_UI_WEBJAR_NAME; +import static org.springdoc.core.utils.Constants.SWAGGER_UI_WEBJAR_NAME_PATTERN; +import static org.springdoc.core.utils.Constants.WEBJARS_RESOURCE_LOCATION; +import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR; + +/** + * The type Abstract swagger configurer. + */ +public abstract class AbstractSwaggerConfigurer { + + /** + * The Swagger ui config properties. + */ + private final SwaggerUiConfigProperties swaggerUiConfigProperties; + + /** + * The Spring Web config properties. + */ + private final WebProperties springWebProperties; + + /** + * The path pattern parser. + */ + private final PathPatternParser parser = new PathPatternParser(); + + /** + * Instantiates a new Abstract swagger configurer. + * + * @param swaggerUiConfigProperties the swagger ui calculated config + * @param springWebProperties the spring web config + */ + protected AbstractSwaggerConfigurer(SwaggerUiConfigProperties swaggerUiConfigProperties, WebProperties springWebProperties) { + this.swaggerUiConfigProperties = swaggerUiConfigProperties; + this.springWebProperties = springWebProperties; + } + + /** + * Gets the handler configs for mapping Swagger UI resources. + * + * @return the Swagger UI handler configs. + */ + protected SwaggerResourceHandlerConfig[] getSwaggerHandlerConfigs() { + String swaggerUiPattern = getUiRootPath() + SWAGGER_UI_PREFIX + ALL_PATTERN; + String swaggerUiInitializerPattern = combinePatterns(swaggerUiPattern, SWAGGER_INITIALIZER_PATTERN); + String swaggerUiResourceLocation = WEBJARS_RESOURCE_LOCATION + SWAGGER_UI_WEBJAR_NAME + DEFAULT_PATH_SEPARATOR + + swaggerUiConfigProperties.getVersion() + DEFAULT_PATH_SEPARATOR; + + return new SwaggerResourceHandlerConfig[]{ + SwaggerResourceHandlerConfig.createCached() + .setPatterns(swaggerUiPattern) + .setLocations(swaggerUiResourceLocation), + SwaggerResourceHandlerConfig.createUncached() + .setPatterns(swaggerUiInitializerPattern) + .setLocations(swaggerUiResourceLocation) + }; + } + + /** + * Gets the handler configs for mapping webjar resources for the Swagger UI. + * + * @return the Swagger UI webjar handler configs. + */ + protected SwaggerResourceHandlerConfig[] getSwaggerWebjarHandlerConfigs() { + if (!springWebProperties.getResources().isAddMappings()) return new SwaggerResourceHandlerConfig[]{}; + + String swaggerUiWebjarPattern = combinePatterns(getWebjarsPathPattern(), SWAGGER_UI_WEBJAR_NAME_PATTERN) + ALL_PATTERN; + String swaggerUiWebjarInitializerPattern = combinePatterns(swaggerUiWebjarPattern, SWAGGER_INITIALIZER_PATTERN); + String swaggerUiWebjarVersionInitializerPattern = combinePatterns(swaggerUiWebjarPattern, + swaggerUiConfigProperties.getVersion() + SWAGGER_INITIALIZER_PATTERN); + String swaggerUiWebjarResourceLocation = WEBJARS_RESOURCE_LOCATION; + + return new SwaggerResourceHandlerConfig[]{ + SwaggerResourceHandlerConfig.createCached() + .setPatterns(swaggerUiWebjarPattern) + .setLocations(swaggerUiWebjarResourceLocation), + SwaggerResourceHandlerConfig.createUncached() + .setPatterns(swaggerUiWebjarInitializerPattern, swaggerUiWebjarVersionInitializerPattern) + .setLocations(swaggerUiWebjarResourceLocation) + }; + } + + /** + * Gets the root path for the Swagger UI. + * + * @return the Swagger UI root path. + */ + protected abstract String getUiRootPath(); + + /** + * Gets the path pattern for webjar resources. + * + * @return the webjars path pattern. + */ + protected abstract String getWebjarsPathPattern(); + + /** + * Combines pattern strings into a new pattern according to the rules of {@link PathPattern#combine}. + * + *

For example: + *

    + *
  • /webjars/** + /swagger-ui/** => /webjars/swagger-ui/**
  • + *
  • /documentation/swagger-ui*/** + /*.js => /documentation/swagger-ui*/*.js
  • + *
+ * + * @param patterns the patterns to combine. + * + * @return the combination of the patterns strings. + * + * @throws IllegalArgumentException if the patterns cannot be combined. + * + * @see PathPattern#combine + */ + protected String combinePatterns(String... patterns) { + return Arrays.stream(patterns) + .map(this::parsePattern) + .reduce(PathPattern::combine) + .map(PathPattern::getPatternString) + .orElseThrow(IllegalArgumentException::new); + } + + /** + * Parses a pattern string as a path pattern. + * + * @param pattern the pattern string. + * + * @return the parsed path pattern. + * + * @throws PatternParseException if the pattern string cannot be parsed. + */ + private PathPattern parsePattern(String pattern) { + return parser.parse(parser.initFullPathPattern(pattern)); + } + + /** + * The type Swagger resource handler config. + * + * @param cacheResources whether to cache resources. + * @param patterns the patterns to match. + * @param locations the locations to use. + */ + protected record SwaggerResourceHandlerConfig(boolean cacheResources, String[] patterns, String[] locations) { + + private SwaggerResourceHandlerConfig(boolean cacheResources) { + this(cacheResources, new String[]{}, new String[]{}); + } + + /** + * Sets the patterns. + * + * @param patterns the patterns to match. + * + * @return the updated config. + */ + public SwaggerResourceHandlerConfig setPatterns(String... patterns) { + return new SwaggerResourceHandlerConfig(cacheResources, patterns, locations); + } + + /** + * Sets the locations. + * + * @param locations the locations to use. + * + * @return the updated config. + */ + public SwaggerResourceHandlerConfig setLocations(String... locations) { + return new SwaggerResourceHandlerConfig(cacheResources, patterns, locations); + } + + /** + * Create a Swagger resource handler config with resource caching enabled. + */ + public static SwaggerResourceHandlerConfig createCached() { + return new SwaggerResourceHandlerConfig(true); + } + + /** + * Create a Swagger resource handler config with resource caching disabled. + */ + public static SwaggerResourceHandlerConfig createUncached() { + return new SwaggerResourceHandlerConfig(false); + } + } +} diff --git a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWebFluxConfigurer.java b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWebFluxConfigurer.java index db07ca427..9e8cd6d0c 100644 --- a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWebFluxConfigurer.java +++ b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWebFluxConfigurer.java @@ -29,27 +29,26 @@ import org.springdoc.core.properties.SwaggerUiConfigParameters; import org.springdoc.core.properties.SwaggerUiConfigProperties; +import org.springdoc.ui.AbstractSwaggerConfigurer; import org.springframework.boot.autoconfigure.web.WebProperties; import org.springframework.boot.webflux.autoconfigure.WebFluxProperties; +import org.springframework.cache.Cache; +import org.springframework.cache.concurrent.ConcurrentMapCache; import org.springframework.http.CacheControl; +import org.springframework.web.reactive.config.ResourceChainRegistration; +import org.springframework.web.reactive.config.ResourceHandlerRegistration; import org.springframework.web.reactive.config.ResourceHandlerRegistry; import org.springframework.web.reactive.config.WebFluxConfigurer; -import org.springframework.web.util.pattern.PathPattern; -import org.springframework.web.util.pattern.PathPatternParser; -import static org.springdoc.core.utils.Constants.ALL_PATTERN; -import static org.springdoc.core.utils.Constants.SWAGGER_INITIALIZER_PATTERN; -import static org.springdoc.core.utils.Constants.SWAGGER_UI_PREFIX; -import static org.springdoc.core.utils.Constants.SWAGGER_UI_WEBJAR_NAME; -import static org.springdoc.core.utils.Constants.SWAGGER_UI_WEBJAR_NAME_PATTERN; -import static org.springdoc.core.utils.Constants.WEBJARS_RESOURCE_LOCATION; -import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR; +import org.springframework.web.reactive.resource.CachingResourceResolver; + +import static org.springdoc.core.utils.Constants.SWAGGER_RESOURCE_CACHE_NAME; /** * The type Swagger web flux configurer. * * @author bnasslahsen */ -public class SwaggerWebFluxConfigurer implements WebFluxConfigurer { +public class SwaggerWebFluxConfigurer extends AbstractSwaggerConfigurer implements WebFluxConfigurer { /** * The Swagger index transformer. @@ -66,11 +65,6 @@ public class SwaggerWebFluxConfigurer implements WebFluxConfigurer { */ private final SwaggerUiConfigProperties swaggerUiConfigProperties; - /** - * The Spring Web config properties. - */ - private final WebProperties springWebProperties; - /** * The Spring WebFlux config properties. */ @@ -81,7 +75,10 @@ public class SwaggerWebFluxConfigurer implements WebFluxConfigurer { */ private final SwaggerWelcomeCommon swaggerWelcomeCommon; - private final PathPatternParser parser = new PathPatternParser(); + /** + * The Swagger resource chain cache. + */ + private Cache cache; /** * Instantiates a new Swagger web flux configurer. @@ -91,63 +88,64 @@ public class SwaggerWebFluxConfigurer implements WebFluxConfigurer { * @param springWebFluxProperties the spring webflux config * @param swaggerIndexTransformer the swagger index transformer * @param swaggerResourceResolver the swagger resource resolver - * @param swaggerWelcomeCommon the swagger welcome common + * @param swaggerWelcomeCommon the swagger welcome common */ public SwaggerWebFluxConfigurer(SwaggerUiConfigProperties swaggerUiConfigProperties, WebProperties springWebProperties, WebFluxProperties springWebFluxProperties, SwaggerIndexTransformer swaggerIndexTransformer, SwaggerResourceResolver swaggerResourceResolver, SwaggerWelcomeCommon swaggerWelcomeCommon) { + super(swaggerUiConfigProperties, springWebProperties); this.swaggerIndexTransformer = swaggerIndexTransformer; this.swaggerResourceResolver = swaggerResourceResolver; this.swaggerUiConfigProperties = swaggerUiConfigProperties; - this.springWebProperties = springWebProperties; this.springWebFluxProperties = springWebFluxProperties; this.swaggerWelcomeCommon = swaggerWelcomeCommon; } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { - String swaggerUiPattern = getUiRootPath() + SWAGGER_UI_PREFIX + ALL_PATTERN; - String swaggerUiResourceLocation = WEBJARS_RESOURCE_LOCATION + SWAGGER_UI_WEBJAR_NAME + DEFAULT_PATH_SEPARATOR + - swaggerUiConfigProperties.getVersion() + DEFAULT_PATH_SEPARATOR; - - addSwaggerUiResourceHandler(registry, swaggerUiPattern, swaggerUiResourceLocation); - - // Add custom mappings for Swagger UI WebJar resources if Spring resource mapping is enabled - if (springWebProperties.getResources().isAddMappings()) { - String webjarsPathPattern = springWebFluxProperties.getWebjarsPathPattern(); - - String swaggerUiWebjarPattern = mergePatterns(webjarsPathPattern, SWAGGER_UI_WEBJAR_NAME_PATTERN) + ALL_PATTERN; - String swaggerUiWebjarResourceLocation = WEBJARS_RESOURCE_LOCATION; - - addSwaggerUiResourceHandler(registry, swaggerUiWebjarPattern, swaggerUiWebjarResourceLocation); - } + addSwaggerResourceHandlers(registry, getSwaggerHandlerConfigs()); + addSwaggerResourceHandlers(registry, getSwaggerWebjarHandlerConfigs()); } /** - * Adds the resource handlers for serving the Swagger UI resources. + * Add resource handlers that use the Swagger resource resolver and transformer. + * + * @param registry the resource handler registry. + * @param handlerConfigs the swagger handler configs. */ - protected void addSwaggerUiResourceHandler(ResourceHandlerRegistry registry, String pattern, String... resourceLocations) { - registry.addResourceHandler(pattern) - .addResourceLocations(resourceLocations) - .resourceChain(false) - .addResolver(swaggerResourceResolver) - .addTransformer(swaggerIndexTransformer); - - // Ensure Swagger initializer has "no-store" Cache-Control header - registry.addResourceHandler(mergePatterns(pattern, SWAGGER_INITIALIZER_PATTERN)) - .setCacheControl(CacheControl.noStore()) - .addResourceLocations(resourceLocations) - .resourceChain(false) - .addResolver(swaggerResourceResolver) - .addTransformer(swaggerIndexTransformer); + protected void addSwaggerResourceHandlers(ResourceHandlerRegistry registry, SwaggerResourceHandlerConfig... handlerConfigs) { + for (SwaggerResourceHandlerConfig handlerConfig : handlerConfigs) { + addSwaggerResourceHandler(registry, handlerConfig); + } } /** - * Computes and returns the root path for the Swagger UI. + * Add a resource handler that uses the Swagger resource resolver and transformer. * - * @return the Swagger UI root path. + * @param registry the resource handler registry. + * @param handlerConfig the swagger handler config. */ + protected void addSwaggerResourceHandler(ResourceHandlerRegistry registry, SwaggerResourceHandlerConfig handlerConfig) { + ResourceHandlerRegistration handlerRegistration = registry.addResourceHandler(handlerConfig.patterns()); + handlerRegistration.addResourceLocations(handlerConfig.locations()); + + ResourceChainRegistration chainRegistration; + if (handlerConfig.cacheResources()) { + chainRegistration = handlerRegistration.resourceChain(true, getCache()); + } else { + handlerRegistration.setUseLastModified(false); + handlerRegistration.setCacheControl(CacheControl.noStore()); + + chainRegistration = handlerRegistration.resourceChain(false); + chainRegistration.addResolver(new CachingResourceResolver(getCache())); // only use cache for resolving + } + + chainRegistration.addResolver(swaggerResourceResolver); + chainRegistration.addTransformer(swaggerIndexTransformer); + } + + @Override protected String getUiRootPath() { SwaggerUiConfigParameters swaggerUiConfigParameters = new SwaggerUiConfigParameters(swaggerUiConfigProperties); swaggerWelcomeCommon.calculateUiRootPath(swaggerUiConfigParameters); @@ -155,26 +153,19 @@ protected String getUiRootPath() { return swaggerUiConfigParameters.getUiRootPath(); } + @Override + protected String getWebjarsPathPattern() { + return springWebFluxProperties.getWebjarsPathPattern(); + } + /** - * Combines two patterns into a new pattern according to the rules of {@link PathPattern#combine}. - * - *

For example: - *

    - *
  • /webjars/** + /swagger-ui/** => /webjars/swagger-ui/**
  • - *
  • /documentation/swagger-ui*/** + /*.js => /documentation/swagger-ui*/*.js
  • - *
- * - * @param pattern1 the first pattern - * @param pattern2 the second pattern - * - * @return the combination of the two patterns + * Gets the Swagger resource chain cache. * - * @see PathPattern#combine + * @return the cache. */ - private String mergePatterns(String pattern1, String pattern2) { - PathPattern pathPattern1 = parser.parse(parser.initFullPathPattern(pattern1)); - PathPattern pathPattern2 = parser.parse(parser.initFullPathPattern(pattern2)); + protected Cache getCache() { + if (cache == null) cache = new ConcurrentMapCache(SWAGGER_RESOURCE_CACHE_NAME); - return pathPattern1.combine(pathPattern2).getPatternString(); + return cache; } } diff --git a/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWebMvcConfigurer.java b/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWebMvcConfigurer.java index 621779138..4ef8305b8 100644 --- a/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWebMvcConfigurer.java +++ b/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWebMvcConfigurer.java @@ -31,8 +31,11 @@ import org.springdoc.core.properties.SwaggerUiConfigParameters; import org.springdoc.core.properties.SwaggerUiConfigProperties; +import org.springdoc.ui.AbstractSwaggerConfigurer; import org.springframework.boot.autoconfigure.web.WebProperties; import org.springframework.boot.webmvc.autoconfigure.WebMvcProperties; +import org.springframework.cache.Cache; +import org.springframework.cache.concurrent.ConcurrentMapCache; import org.springframework.format.FormatterRegistry; import org.springframework.http.CacheControl; import org.springframework.http.converter.HttpMessageConverter; @@ -48,27 +51,22 @@ import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; +import org.springframework.web.servlet.config.annotation.ResourceChainRegistration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.ViewResolverRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import org.springframework.web.util.pattern.PathPattern; -import org.springframework.web.util.pattern.PathPatternParser; +import org.springframework.web.servlet.resource.CachingResourceResolver; -import static org.springdoc.core.utils.Constants.ALL_PATTERN; -import static org.springdoc.core.utils.Constants.SWAGGER_INITIALIZER_PATTERN; -import static org.springdoc.core.utils.Constants.SWAGGER_UI_PREFIX; -import static org.springdoc.core.utils.Constants.SWAGGER_UI_WEBJAR_NAME; -import static org.springdoc.core.utils.Constants.SWAGGER_UI_WEBJAR_NAME_PATTERN; -import static org.springdoc.core.utils.Constants.WEBJARS_RESOURCE_LOCATION; -import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR; +import static org.springdoc.core.utils.Constants.SWAGGER_RESOURCE_CACHE_NAME; /** * The type Swagger web mvc configurer. * * @author bnasslahsen */ -public class SwaggerWebMvcConfigurer implements WebMvcConfigurer { +public class SwaggerWebMvcConfigurer extends AbstractSwaggerConfigurer implements WebMvcConfigurer { /** * The Swagger index transformer. @@ -85,11 +83,6 @@ public class SwaggerWebMvcConfigurer implements WebMvcConfigurer { */ private final SwaggerUiConfigProperties swaggerUiConfigProperties; - /** - * The Spring Web config properties. - */ - private final WebProperties springWebProperties; - /** * The Spring MVC config properties. */ @@ -100,7 +93,10 @@ public class SwaggerWebMvcConfigurer implements WebMvcConfigurer { */ private final SwaggerWelcomeCommon swaggerWelcomeCommon; - private final PathPatternParser parser = new PathPatternParser(); + /** + * The Swagger resource chain cache. + */ + private Cache cache; /** * Instantiates a new Swagger web mvc configurer. @@ -110,63 +106,64 @@ public class SwaggerWebMvcConfigurer implements WebMvcConfigurer { * @param springWebMvcProperties the spring mvc config * @param swaggerIndexTransformer the swagger index transformer * @param swaggerResourceResolver the swagger resource resolver - * @param swaggerWelcomeCommon the swagger welcome common + * @param swaggerWelcomeCommon the swagger welcome common */ public SwaggerWebMvcConfigurer(SwaggerUiConfigProperties swaggerUiConfigProperties, WebProperties springWebProperties, WebMvcProperties springWebMvcProperties, SwaggerIndexTransformer swaggerIndexTransformer, SwaggerResourceResolver swaggerResourceResolver, SwaggerWelcomeCommon swaggerWelcomeCommon) { + super(swaggerUiConfigProperties, springWebProperties); this.swaggerIndexTransformer = swaggerIndexTransformer; this.swaggerResourceResolver = swaggerResourceResolver; this.swaggerUiConfigProperties = swaggerUiConfigProperties; - this.springWebProperties = springWebProperties; this.springWebMvcProperties = springWebMvcProperties; this.swaggerWelcomeCommon = swaggerWelcomeCommon; } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { - String swaggerUiPattern = getUiRootPath() + SWAGGER_UI_PREFIX + ALL_PATTERN; - String swaggerUiResourceLocation = WEBJARS_RESOURCE_LOCATION + SWAGGER_UI_WEBJAR_NAME + DEFAULT_PATH_SEPARATOR + - swaggerUiConfigProperties.getVersion() + DEFAULT_PATH_SEPARATOR; - - addSwaggerUiResourceHandler(registry, swaggerUiPattern, swaggerUiResourceLocation); - - // Add custom mappings for Swagger UI WebJar resources if Spring resource mapping is enabled - if (springWebProperties.getResources().isAddMappings()) { - String webjarsPathPattern = springWebMvcProperties.getWebjarsPathPattern(); - - String swaggerUiWebjarPattern = mergePatterns(webjarsPathPattern, SWAGGER_UI_WEBJAR_NAME_PATTERN) + ALL_PATTERN; - String swaggerUiWebjarResourceLocation = WEBJARS_RESOURCE_LOCATION; - - addSwaggerUiResourceHandler(registry, swaggerUiWebjarPattern, swaggerUiWebjarResourceLocation); - } + addSwaggerResourceHandlers(registry, getSwaggerHandlerConfigs()); + addSwaggerResourceHandlers(registry, getSwaggerWebjarHandlerConfigs()); } /** - * Adds the resource handlers for serving the Swagger UI resources. + * Add resource handlers that use the Swagger resource resolver and transformer. + * + * @param registry the resource handler registry. + * @param handlerConfigs the swagger handler configs. */ - protected void addSwaggerUiResourceHandler(ResourceHandlerRegistry registry, String pattern, String... resourceLocations) { - registry.addResourceHandler(pattern) - .addResourceLocations(resourceLocations) - .resourceChain(false) - .addResolver(swaggerResourceResolver) - .addTransformer(swaggerIndexTransformer); - - // Ensure Swagger initializer has "no-store" Cache-Control header - registry.addResourceHandler(mergePatterns(pattern, SWAGGER_INITIALIZER_PATTERN)) - .setCacheControl(CacheControl.noStore()) - .addResourceLocations(resourceLocations) - .resourceChain(false) - .addResolver(swaggerResourceResolver) - .addTransformer(swaggerIndexTransformer); + protected void addSwaggerResourceHandlers(ResourceHandlerRegistry registry, SwaggerResourceHandlerConfig... handlerConfigs) { + for (SwaggerResourceHandlerConfig handlerConfig : handlerConfigs) { + addSwaggerResourceHandler(registry, handlerConfig); + } } /** - * Computes and returns the root path for the Swagger UI. + * Add a resource handler that uses the Swagger resource resolver and transformer. * - * @return the Swagger UI root path. + * @param registry the resource handler registry. + * @param handlerConfig the swagger handler config. */ + protected void addSwaggerResourceHandler(ResourceHandlerRegistry registry, SwaggerResourceHandlerConfig handlerConfig) { + ResourceHandlerRegistration handlerRegistration = registry.addResourceHandler(handlerConfig.patterns()); + handlerRegistration.addResourceLocations(handlerConfig.locations()); + + ResourceChainRegistration chainRegistration; + if (handlerConfig.cacheResources()) { + chainRegistration = handlerRegistration.resourceChain(true, getCache()); + } else { + handlerRegistration.setUseLastModified(false); + handlerRegistration.setCacheControl(CacheControl.noStore()); + + chainRegistration = handlerRegistration.resourceChain(false); + chainRegistration.addResolver(new CachingResourceResolver(getCache())); // only use cache for resolving + } + + chainRegistration.addResolver(swaggerResourceResolver); + chainRegistration.addTransformer(swaggerIndexTransformer); + } + + @Override protected String getUiRootPath() { SwaggerUiConfigParameters swaggerUiConfigParameters = new SwaggerUiConfigParameters(swaggerUiConfigProperties); swaggerWelcomeCommon.calculateUiRootPath(swaggerUiConfigParameters); @@ -174,27 +171,20 @@ protected String getUiRootPath() { return swaggerUiConfigParameters.getUiRootPath(); } + @Override + protected String getWebjarsPathPattern() { + return springWebMvcProperties.getWebjarsPathPattern(); + } + /** - * Combines two patterns into a new pattern according to the rules of {@link PathPattern#combine}. - * - *

For example: - *

    - *
  • /webjars/** + /swagger-ui/** => /webjars/swagger-ui/**
  • - *
  • /documentation/swagger-ui*/** + /*.js => /documentation/swagger-ui*/*.js
  • - *
- * - * @param pattern1 the first pattern - * @param pattern2 the second pattern - * - * @return the combination of the two patterns + * Gets the Swagger resource chain cache. * - * @see PathPattern#combine + * @return the cache. */ - private String mergePatterns(String pattern1, String pattern2) { - PathPattern pathPattern1 = parser.parse(parser.initFullPathPattern(pattern1)); - PathPattern pathPattern2 = parser.parse(parser.initFullPathPattern(pattern2)); + protected Cache getCache() { + if (cache == null) cache = new ConcurrentMapCache(SWAGGER_RESOURCE_CACHE_NAME); - return pathPattern1.combine(pathPattern2).getPatternString(); + return cache; } @Override From 3cb307adb62424d706eff3668729418896c8e8ea Mon Sep 17 00:00:00 2001 From: James Missen <88438228+jamesmissen@users.noreply.github.com> Date: Sat, 29 Nov 2025 12:43:41 +1300 Subject: [PATCH 6/6] Simplify building from context path using ServletRequestPathUtils --- .../org/springdoc/webflux/ui/SwaggerWelcomeCommon.java | 2 +- .../org/springdoc/webmvc/ui/SwaggerWelcomeCommon.java | 10 ++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeCommon.java b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeCommon.java index a53c42408..6b810491d 100644 --- a/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeCommon.java +++ b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerWelcomeCommon.java @@ -110,7 +110,7 @@ protected Map getSwaggerUiConfig(ServerWebExchange exchange) { * @param swaggerUiConfigParameters the swagger ui config parameters * @param exchange the exchange */ - void buildFromCurrentContextPath(SwaggerUiConfigParameters swaggerUiConfigParameters, ServerWebExchange exchange) { + protected void buildFromCurrentContextPath(SwaggerUiConfigParameters swaggerUiConfigParameters, ServerWebExchange exchange) { super.init(swaggerUiConfigParameters); String contextPath = exchange.getRequest().getPath().contextPath().value(); diff --git a/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWelcomeCommon.java b/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWelcomeCommon.java index 5f47f2f4a..b82245567 100644 --- a/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWelcomeCommon.java +++ b/springdoc-openapi-starter-webmvc-ui/src/main/java/org/springdoc/webmvc/ui/SwaggerWelcomeCommon.java @@ -41,8 +41,8 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.util.ForwardedHeaderUtils; +import org.springframework.web.util.ServletRequestPathUtils; import org.springframework.web.util.UriComponentsBuilder; -import org.springframework.web.util.UrlPathHelper; /** * The type Swagger welcome common. @@ -51,8 +51,6 @@ */ public abstract class SwaggerWelcomeCommon extends AbstractSwaggerWelcome { - private final UrlPathHelper urlPathHelper = new UrlPathHelper(); - /** * Instantiates a new Abstract swagger welcome. * @@ -112,11 +110,11 @@ protected void calculateOauth2RedirectUrl(SwaggerUiConfigParameters swaggerUiCon * @param swaggerUiConfigParameters the swagger ui config parameters * @param request the request */ - void buildFromCurrentContextPath(SwaggerUiConfigParameters swaggerUiConfigParameters, HttpServletRequest request) { + protected void buildFromCurrentContextPath(SwaggerUiConfigParameters swaggerUiConfigParameters, HttpServletRequest request) { super.init(swaggerUiConfigParameters); - String pathWithinServletMapping = urlPathHelper.getPathWithinServletMapping(request); - String contextPath = request.getContextPath() + (!StringUtils.isBlank(pathWithinServletMapping) ? request.getServletPath() : ""); + String servletPath = ServletRequestPathUtils.getServletPathPrefix(request); + String contextPath = request.getContextPath() + (servletPath != null ? servletPath : ""); swaggerUiConfigParameters.setContextPath(contextPath); URI uri = URI.create(request.getRequestURL().toString());