WebFlux doesn't support version-agnostic WebJars (without hard-coding their version number in the code) to be hosted from a static resource path if it already specifies the specific WebJar name (e.g. swagger-ui
) in the fixed part of the path; but it does work when its name is contained in the "variable part" of the path. Of course, this isn't as important as the fact that this isn't documented anywhere, making users spend days debugging an issue caused by a simple code refactoring, as it behaves ambiguously under different circumstances, requiring learning the entire Spring internal implementation to even understand the issue.
Here is a reproducible example where a mapping localhost:8080/swagger-ui/index.html
doesn't work:
Hidden import statements
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.config.ResourceHandlerRegistry;
import org.springframework.web.reactive.config.WebFluxConfigurer;
@Configuration
public class WebConfiguration {
@Bean
public WebFluxConfigurer webConfigurer() {
return new WebFluxConfigurer() {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/swagger-ui/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/swagger-ui/")
.resourceChain(false);
}
};
}
}
Under the Spring implementation, the variables are set as follows:
requestPath
= "index.html
"locations
= List ["META-INF/resources/webjars/swagger-ui/
"]
when resolveResourceInternal
is called (from ResourceWebHandler.handle
), executed at
https://github.com/spring-projects/spring-framework/blob/6fbd4841ec4a20cfd8c5338c6bcf0171839c2326/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/WebJarsResourceResolver.java#L80
which calls findWebJarResourcePath
with a path
parameter having value "index.html
" on line
https://github.com/spring-projects/spring-framework/blob/6fbd4841ec4a20cfd8c5338c6bcf0171839c2326/spring-webmvc/src/main/java/org/springframework/web/servlet/resource/WebJarsResourceResolver.java#L103
This function wrongly assumes that the path
parameter contains a path like swagger-ui/index.html
instead, thus it fails and returns null. As a first possible workaround that I see, the path
could be concatenated with the content of locations
; but I don't have any knowledge of the internals or motivations behind the current approach. At the very least, some parameter could be exposed to allow explicitly adding one level of a parent hierarchy.
- By the way, I noticed that even whenever a path doesn't contain any webjars reference, e.g. when simply using
registry.addResourceHandler("/something/**").addResourceLocations("classpath:/META-INF/resources/").resourceChain(false);
, and the user requests a page likelocalhost:8080/something/swagger-ui/index.html
, the aforementioned code is still fully executed, calculating the entire swagger-ui WebJars path and properly returning it (though it ends up as a 404 Not Found page in the end, so it isn't necessarily a bug) for no reason, which could be avoided in my opinion; maybe this could be addressed together with this issue.
Background
- WebJars project allows you to specify your web libraries (JavaScript, CSS, etc) as dependencies in your Java web applications. < https://jamesward.com/2012/04/30/webjars-in-spring-mvc/ >
- Spring MVC makes it easy to expose static assets in JAR files using ResourceHandlers. < https://www.webjars.org/documentation#springmvc >
- The resourceChain() method must be called for version-agnostic WebJars. < http://zetcode.com/spring/webjars/ >
- This is used by WebFluxAutoConfiguration by default.
- Spring Boot documentation about hosting static content. To use version agnostic URLs for Webjars, add the webjars-locator-core dependency
- Some resources mention that we need to include
org.webjars:webjars-locator
as a dependency (Maven, Gradle), while other mention simply theorg.webjars:webjars-locator-core
.
An example of a correctly working usage can be like this, where we refer to the WebJars via the /webjars/ path.
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/webjars/**")
.addResourceLocations("/webjars/").resourceChain(false);
}
And of course, security has to be set up like
.pathMatchers(HttpMethod.GET, "/webjars/**").permitAll()
Affects: spring-webflux-5.2.7
Comment From: jacodg
I've submitted https://github.com/webjars/webjars/issues/1395 to the Webjars project after this issue giving me a clue on why version agnostic url's were not working in my Spring MVC setup. The PR adds the resourceChain() method to the documentation and changes webjars-locator to webjars-locator-core because I think only webjars-locator is needed (adding webjars-locator does work but only because it will add webjars-locator-core as a transitive dependency)
Comment From: dsyer
It works for me out of the box with Spring Boot, but you have to put /webjars/
at the front of the resource path. E.g. <script src="/webjars/jquery/jquery.min.js"></script>
. Spring Boot has some autoconfiguration for the webjars-locator. I believe this issue should be closed.
Comment From: bclozel
Thanks @dsyer - you're right, this has been superseded by webjars/webjars#1395 and dependency management has been aligned as well in Spring Boot. Closing.