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-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 1be092419..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,18 +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 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.CLASSPATH_RESOURCE_LOCATION; -import static org.springdoc.core.utils.Constants.DEFAULT_WEB_JARS_PREFIX_URL; +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; /** @@ -69,64 +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; - String webjarsPrefix = springDocConfigProperties.getWebjars().getPrefix(); - String resourcePath, swaggerUiPrefix, swaggerUiWebjarsPrefix; + addSwaggerUiResourceHandler(registry, swaggerUiPattern, swaggerUiResourceLocation); - 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; + // 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); } + } - registry.addResourceHandler(uiRootPath + swaggerUiWebjarsPrefix + ALL_PATTERN) - .addResourceLocations(CLASSPATH_RESOURCE_LOCATION + resourcePath) + /** + * 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 + swaggerUiPrefix + ALL_PATTERN) - .addResourceLocations(CLASSPATH_RESOURCE_LOCATION + resourcePath) + // 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-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/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-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": "" } 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. 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 {} + +}