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..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 @@ -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,21 @@ 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 SWAGGER_RESOURCE_CACHE_NAME. + */ + public static final String SWAGGER_RESOURCE_CACHE_NAME = "swagger-resource-chain-cache"; + /** * The constant HEALTH_PATTERN. */ @@ -397,7 +415,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-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: + *

+ * + * @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/SwaggerConfig.java b/springdoc-openapi-starter-webflux-ui/src/main/java/org/springdoc/webflux/ui/SwaggerConfig.java index f9f1eddb2..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; @@ -47,6 +46,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,19 +121,21 @@ 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 + * @param swaggerWelcomeCommon the swagger welcome common * @return the swagger web flux configurer */ @Bean @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, 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 1be092419..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 @@ -26,38 +26,35 @@ package org.springdoc.webflux.ui; -import java.util.Optional; - -import org.springdoc.core.properties.SpringDocConfigProperties; +import org.springdoc.core.properties.SwaggerUiConfigParameters; import org.springdoc.core.properties.SwaggerUiConfigProperties; -import org.springdoc.core.providers.ActuatorProvider; +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.reactive.resource.CachingResourceResolver; -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_UI_PREFIX; -import static org.springframework.util.AntPathMatcher.DEFAULT_PATH_SEPARATOR; +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. */ private final SwaggerIndexTransformer swaggerIndexTransformer; - /** - * The Actuator provider. - */ - private final Optional actuatorProvider; - /** * The Swagger resource resolver. */ @@ -69,64 +66,106 @@ public class SwaggerWebFluxConfigurer implements WebFluxConfigurer { private final SwaggerUiConfigProperties swaggerUiConfigProperties; /** - * The Spring doc config properties. + * The Spring WebFlux config properties. + */ + private final WebFluxProperties springWebFluxProperties; + + /** + * The Swagger welcome common. + */ + private final SwaggerWelcomeCommon swaggerWelcomeCommon; + + /** + * The Swagger resource chain cache. */ - private final SpringDocConfigProperties springDocConfigProperties; + private Cache cache; /** * 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 + * @param swaggerWelcomeCommon the swagger welcome common */ public SwaggerWebFluxConfigurer(SwaggerUiConfigProperties swaggerUiConfigProperties, - SpringDocConfigProperties springDocConfigProperties, - SwaggerIndexTransformer swaggerIndexTransformer, - Optional actuatorProvider, SwaggerResourceResolver swaggerResourceResolver) { + WebProperties springWebProperties, WebFluxProperties springWebFluxProperties, + SwaggerIndexTransformer swaggerIndexTransformer, SwaggerResourceResolver swaggerResourceResolver, + SwaggerWelcomeCommon swaggerWelcomeCommon) { + super(swaggerUiConfigProperties, springWebProperties); this.swaggerIndexTransformer = swaggerIndexTransformer; - this.actuatorProvider = actuatorProvider; this.swaggerResourceResolver = swaggerResourceResolver; this.swaggerUiConfigProperties = swaggerUiConfigProperties; - this.springDocConfigProperties = springDocConfigProperties; + this.springWebFluxProperties = springWebFluxProperties; + this.swaggerWelcomeCommon = swaggerWelcomeCommon; } @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 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; + addSwaggerResourceHandlers(registry, getSwaggerHandlerConfigs()); + addSwaggerResourceHandlers(registry, getSwaggerWebjarHandlerConfigs()); + } + + /** + * 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 addSwaggerResourceHandlers(ResourceHandlerRegistry registry, SwaggerResourceHandlerConfig... handlerConfigs) { + for (SwaggerResourceHandlerConfig handlerConfig : handlerConfigs) { + addSwaggerResourceHandler(registry, handlerConfig); } - else { - swaggerUiPrefix = webjarsPrefix; - resourcePath = DEFAULT_WEB_JARS_PREFIX_URL + DEFAULT_PATH_SEPARATOR; - swaggerUiWebjarsPrefix = swaggerUiPrefix; + } + + /** + * Add a resource handler that uses the Swagger resource resolver and transformer. + * + * @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 } - registry.addResourceHandler(uiRootPath + swaggerUiWebjarsPrefix + ALL_PATTERN) - .addResourceLocations(CLASSPATH_RESOURCE_LOCATION + resourcePath) - .resourceChain(false) - .addResolver(swaggerResourceResolver) - .addTransformer(swaggerIndexTransformer); - - registry.addResourceHandler(uiRootPath + swaggerUiPrefix + ALL_PATTERN) - .addResourceLocations(CLASSPATH_RESOURCE_LOCATION + resourcePath) - .resourceChain(false) - .addResolver(swaggerResourceResolver) - .addTransformer(swaggerIndexTransformer); + chainRegistration.addResolver(swaggerResourceResolver); + chainRegistration.addTransformer(swaggerIndexTransformer); + } + + @Override + protected String getUiRootPath() { + SwaggerUiConfigParameters swaggerUiConfigParameters = new SwaggerUiConfigParameters(swaggerUiConfigProperties); + swaggerWelcomeCommon.calculateUiRootPath(swaggerUiConfigParameters); + + return swaggerUiConfigParameters.getUiRootPath(); } + @Override + protected String getWebjarsPathPattern() { + return springWebFluxProperties.getWebjarsPathPattern(); + } + + /** + * Gets the Swagger resource chain cache. + * + * @return the cache. + */ + protected Cache getCache() { + if (cache == null) cache = new ConcurrentMapCache(SWAGGER_RESOURCE_CACHE_NAME); + + return cache; + } } 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 87660e686..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 @@ -34,16 +34,14 @@ 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; -import static org.springdoc.core.utils.Constants.DEFAULT_WEB_JARS_PREFIX_URL; - /** * The type Swagger welcome common. * @@ -65,52 +63,44 @@ 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); - String webjarsPrefix = springDocConfigProperties.getWebjars().getPrefix(); - String additionalPrefix = DEFAULT_WEB_JARS_PREFIX_URL.equals(webjarsPrefix) ? "" : webjarsPrefix; - String sbUrl = swaggerUiConfigParameters.getContextPath() - + swaggerUiConfigParameters.getUiRootPath() - + additionalPrefix - + getSwaggerUiUrl(); + 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); - response.setStatusCode(HttpStatus.FOUND); - response.getHeaders().setLocation(URI.create(uriBuilder.build().encode().toString())); - return response.setComplete(); + exchange.getRequest().getQueryParams().forEach(uriBuilder::queryParam); + + exchange.getResponse().setStatusCode(HttpStatus.FOUND); + exchange.getResponse().getHeaders().setLocation(URI.create(uriBuilder.build().encode().toString())); + return exchange.getResponse().setComplete(); } @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()); } } + @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(); } @@ -118,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) { + protected 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-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..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; @@ -47,7 +46,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,17 +149,21 @@ 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 + * @param swaggerWelcomeCommon the swagger welcome common * @return the swagger web mvc configurer */ @Bean @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, 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 981eb4205..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 @@ -27,12 +27,17 @@ 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.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; import org.springframework.lang.Nullable; import org.springframework.validation.MessageCodesResolver; @@ -46,23 +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.servlet.resource.CachingResourceResolver; -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; +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. @@ -70,9 +74,9 @@ public class SwaggerWebMvcConfigurer implements WebMvcConfigurer { private final SwaggerIndexTransformer swaggerIndexTransformer; /** - * The Actuator provider. + * The Swagger resource resolver. */ - private final Optional actuatorProvider; + private final SwaggerResourceResolver swaggerResourceResolver; /** * The Swagger ui config properties. @@ -80,48 +84,107 @@ public class SwaggerWebMvcConfigurer implements WebMvcConfigurer { private final SwaggerUiConfigProperties swaggerUiConfigProperties; /** - * The Swagger resource resolver. + * The Spring MVC config properties. */ - private final SwaggerResourceResolver swaggerResourceResolver; + private final WebMvcProperties springWebMvcProperties; + + /** + * The Swagger welcome common. + */ + private final SwaggerWelcomeCommon swaggerWelcomeCommon; + + /** + * The Swagger resource chain cache. + */ + private Cache cache; /** * 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 + * @param swaggerWelcomeCommon the swagger welcome common */ public SwaggerWebMvcConfigurer(SwaggerUiConfigProperties swaggerUiConfigProperties, - SwaggerIndexTransformer swaggerIndexTransformer, - Optional actuatorProvider, SwaggerResourceResolver swaggerResourceResolver) { + WebProperties springWebProperties, WebMvcProperties springWebMvcProperties, + SwaggerIndexTransformer swaggerIndexTransformer, SwaggerResourceResolver swaggerResourceResolver, + SwaggerWelcomeCommon swaggerWelcomeCommon) { + super(swaggerUiConfigProperties, springWebProperties); this.swaggerIndexTransformer = swaggerIndexTransformer; - this.actuatorProvider = actuatorProvider; this.swaggerResourceResolver = swaggerResourceResolver; this.swaggerUiConfigProperties = swaggerUiConfigProperties; + this.springWebMvcProperties = springWebMvcProperties; + this.swaggerWelcomeCommon = swaggerWelcomeCommon; } @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()); - - registry.addResourceHandler(uiRootPath + SWAGGER_UI_PREFIX + "*/*" + SWAGGER_INITIALIZER_JS) - .addResourceLocations(CLASSPATH_RESOURCE_LOCATION + DEFAULT_WEB_JARS_PREFIX_URL + DEFAULT_PATH_SEPARATOR) - .setCachePeriod(0) - .resourceChain(false) - .addResolver(swaggerResourceResolver) - .addTransformer(swaggerIndexTransformer); - - registry.addResourceHandler(uiRootPath + SWAGGER_UI_PREFIX + "*/**") - .addResourceLocations(CLASSPATH_RESOURCE_LOCATION + DEFAULT_WEB_JARS_PREFIX_URL + DEFAULT_PATH_SEPARATOR) - .resourceChain(false) - .addResolver(swaggerResourceResolver) - .addTransformer(swaggerIndexTransformer); + addSwaggerResourceHandlers(registry, getSwaggerHandlerConfigs()); + addSwaggerResourceHandlers(registry, getSwaggerWebjarHandlerConfigs()); + } + + /** + * 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 addSwaggerResourceHandlers(ResourceHandlerRegistry registry, SwaggerResourceHandlerConfig... handlerConfigs) { + for (SwaggerResourceHandlerConfig handlerConfig : handlerConfigs) { + addSwaggerResourceHandler(registry, handlerConfig); + } + } + + /** + * Add a resource handler that uses the Swagger resource resolver and transformer. + * + * @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); + + return swaggerUiConfigParameters.getUiRootPath(); + } + + @Override + protected String getWebjarsPathPattern() { + return springWebMvcProperties.getWebjarsPathPattern(); + } + + /** + * Gets the Swagger resource chain cache. + * + * @return the cache. + */ + protected Cache getCache() { + if (cache == null) cache = new ConcurrentMapCache(SWAGGER_RESOURCE_CACHE_NAME); + + return cache; } @Override 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..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 @@ -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,9 +37,11 @@ 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.ServletRequestPathUtils; import org.springframework.web.util.UriComponentsBuilder; /** @@ -46,6 +50,7 @@ * @author bnasslashen */ public abstract class SwaggerWelcomeCommon extends AbstractSwaggerWelcome { + /** * Instantiates a new Abstract swagger welcome. * @@ -96,16 +101,31 @@ protected void calculateOauth2RedirectUrl(SwaggerUiConfigParameters swaggerUiCon .path(getOauth2RedirectUrl()).build().toString()); } + @Override + protected abstract void calculateUiRootPath(SwaggerUiConfigParameters swaggerUiConfigParameters, StringBuilder... sbUrls); + /** * From current context path. * * @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); - swaggerUiConfigParameters.setContextPath(request.getContextPath()); - buildConfigUrl(swaggerUiConfigParameters, ServletUriComponentsBuilder.fromCurrentContextPath()); + + String servletPath = ServletRequestPathUtils.getServletPathPrefix(request); + String contextPath = request.getContextPath() + (servletPath != null ? servletPath : ""); + 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/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 {} + +} 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())