Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ResourceHandlers cannot resolve static resources with certain wildcard patterns #29712

Closed
mufasa1976 opened this issue Dec 18, 2022 · 5 comments
Assignees
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: backported An issue that has been backported to maintenance branches type: bug A general bug
Milestone

Comments

@mufasa1976
Copy link

Bug report

The following Code Snippet worked perfectly within Spring Boot 2.7.6

@Configuration
@EnableWebMvc
public class WebMvcConfiguration implements WebMvcConfigurer {
  private static final String[] ANGULAR_RESOURCES = {
      "/favicon.ico",
      "/main.*.js",
      "/polyfills.*.js",
      "/runtime.*.js",
      "/styles.*.css",
      "/deeppurple-amber.css",
      "/indigo-pink.css",
      "/pink-bluegrey.css",
      "/purple-green.css",
      "/3rdpartylicenses.txt"
  };
  private static final List<Locale> SUPPORTED_LANGUAGES = List.of(Locale.GERMAN, Locale.ENGLISH);
  private static final Locale DEFAULT_LOCALE = Locale.ENGLISH;

  private final String prefix;
  private final Collection<HttpMessageConverter<?>> messageConverters;

  public WebMvcConfiguration(@Value("${spring.thymeleaf.prefix:" + ThymeleafProperties.DEFAULT_PREFIX + "}") String prefix, Collection<HttpMessageConverter<?>> messageConverters) {
    this.prefix = StringUtils.appendIfMissing(prefix, "/");
    this.messageConverters = messageConverters;
  }

  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.setOrder(1);
    registry.addResourceHandler("/webjars/**")
            .addResourceLocations("classpath:/META-INF/resources/webjars/");
    for (Locale language : SUPPORTED_LANGUAGES) {
      final var relativeAngularResources = Stream.of(ANGULAR_RESOURCES)
                                                 .filter(resource -> StringUtils.contains(resource, "*"))
                                                 .map(resource -> "/" + language.getLanguage() + resource)
                                                 .toArray(String[]::new);
      registry.addResourceHandler(relativeAngularResources)
              .addResourceLocations(prefix + language.getLanguage() + "/");

      final var fixedAngularResources = Stream.of(ANGULAR_RESOURCES)
                                              .filter(resource -> !StringUtils.contains(resource, "*"))
                                              .map(resource -> "/" + language.getLanguage() + resource)
                                              .toArray(String[]::new);
      registry.addResourceHandler(fixedAngularResources)
              .addResourceLocations(prefix);

      registry.addResourceHandler("/" + language.getLanguage() + "/assets/**")
              .addResourceLocations(prefix + language.getLanguage() + "/assets/");
    }
  }

  @Override
  public void addViewControllers(ViewControllerRegistry registry) {
    registry.setOrder(2);
    for (Locale language : SUPPORTED_LANGUAGES) {
      registry.addViewController("/" + language.getLanguage() + "/**").setViewName(language.getLanguage() + "/index");
    }
  }

  @Bean
  public RouterFunction<ServerResponse> routerFunction() {
    return route(GET("/"), this::defaultLandingPage);
  }

  private ServerResponse defaultLandingPage(ServerRequest request) {
    final var locale = Optional.ofNullable(Locale.lookup(request.headers().acceptLanguage(), SUPPORTED_LANGUAGES))
                               .orElse(DEFAULT_LOCALE);
    return ServerResponse.status(HttpStatus.TEMPORARY_REDIRECT).render("redirect:/" + locale.getLanguage());
  }

  @Override
  public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    configurer.defaultContentType(APPLICATION_JSON);
  }

  @Override
  public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.addAll(messageConverters);
  }
}

Unfortunatly for Spring Boot 3.0.0 all Wildcard Resources (i.e. main.b35ffcc13cda91f0.js) wouldn't be served. Instead it will result in a HTTP 404 (Not Found).

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Dec 18, 2022
@bclozel
Copy link
Member

bclozel commented Dec 18, 2022

Your code snippet is missing critical information that we need to better understand this case. This doesn't show the resource handling URL pattern nor the request performed.

Could you share a sample application that reproduces the issue? Ideally, an application generated from start.spring.io, containing a single resource handling registration and a single resource.

Thanks!

@bclozel bclozel added the status: waiting-for-feedback We need additional information before we can continue label Dec 18, 2022
@mufasa1976
Copy link
Author

This is already a Github hosted Project: Calcmaster

It is as simple as possible because with this Sample I will demonstrate the possibility to serve an Angular Frontend Application by a Spring Boot Application.

By calling the URL /en/index.html you will see that all Javascript Libraries will result in a HTTP 404 (unfortunatly the used hash will depend on the Machine where the Code will be built why I use the pattern main.*.js)

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Dec 18, 2022
@bclozel
Copy link
Member

bclozel commented Dec 18, 2022

Sorry but there's too much going on there: multi-modules project, docker compose, angular application...
Could you reduce it to a simpler repro as requested?

@bclozel bclozel added status: waiting-for-feedback We need additional information before we can continue and removed status: feedback-provided Feedback has been provided labels Dec 18, 2022
@mufasa1976
Copy link
Author

I just made a simpler Sample which is still a multi-module Maven Project but with a precompiled Angular Application. And I removed everything else (Docker Compose, other Maven Plugins, ...)

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Dec 18, 2022
@bclozel
Copy link
Member

bclozel commented Dec 18, 2022

Thanks for the sample, I've reduced this to the following:

	@Test
	void pathWithinMappingWithPathPattern() {
		String pattern = "/docs/cvs/file.*.html";
		String match = "/docs/cvs/file.sha.html";
		String expectedPath = "file.sha.html";

		PathPatternParser parser = new PathPatternParser();
		PathPattern parsedPattern = parser.parse(pattern);
		String s = parsedPattern.extractPathWithinPattern(toPathContainer(match)).value();
		assertThat(s).isEqualTo(expectedPath);
	}

This is a bug in Spring Framework, I'm transferring the issue as a result.
This has been there for a long time (including in Spring Boot 7), but since your application disables Spring Boot web configuration with @EnableWebMvc, you were relying so far on Framework defaults. Those defaults have changed in Spring Framework 6 with #28607.

To be more precise the issue only happens when getting the path within the handler mapping and the mapping itself contains a .* in the middle of it. In this example, /en/runtime.*.js triggers the issue but /en/runtime*.js will not.

@bclozel bclozel transferred this issue from spring-projects/spring-boot Dec 18, 2022
@bclozel bclozel self-assigned this Dec 18, 2022
@bclozel bclozel added in: web Issues in web modules (web, webmvc, webflux, websocket) type: bug A general bug and removed status: waiting-for-triage An issue we've not yet triaged or decided on status: feedback-provided Feedback has been provided labels Dec 18, 2022
@bclozel bclozel added this to the 6.0.4 milestone Dec 18, 2022
@github-actions github-actions bot added status: backported An issue that has been backported to maintenance branches and removed for: backport-to-5.3.x labels Dec 19, 2022
bclozel added a commit that referenced this issue Dec 19, 2022
Prior to this commit, extracting the path within handler mapping would
result in "" if the matching path element would be a Regex and contain
".*". This could cause issues with resource handling if the handler
mapping pattern was similar to `"/folder/file.*.extension"`.

This commit introduces a new `isLiteral()` method in the `PathElement`
abstract class that expresses whether the path element can be compared
as a String for path matching or if it requires a more elaborate
matching process.

Using this method for extracting the path within handler mapping avoids
relying on wildcard count or other properties.

See gh-29712
Fixes gh-29716
@sbrannen sbrannen changed the title ResourceHandlers wont resolve static Wildcard Resources ResourceHandlers cannot resolve static resources with certain wildcard patterns Dec 19, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: web Issues in web modules (web, webmvc, webflux, websocket) status: backported An issue that has been backported to maintenance branches type: bug A general bug
Projects
None yet
Development

No branches or pull requests

3 participants