Tomcat appends welcome file when a directory with trailing slash is requested. For example, http://localhost:8080/app/test/ is converted into http://localhost:8080/app/test/index.html if index.html exists.

This has been working fine with Spring Boot till we upgraded from Spring Boot 2.2.5.RELEASE to 2.4.5. The new version ignores the index.html and returns 404 for the directory requests.

I traced down the problem to the change in UrlPathHelper used by SimpleUrlHandlerMapping for the webjars,

    public String getLookupPathForRequest(HttpServletRequest request) {
        String pathWithinApp = getPathWithinApplication(request);
        // Always use full path within current servlet context?
        if (this.alwaysUseFullPath || skipServletPathDetermination(request)) {
            return pathWithinApp;
        }
        // Else, use path within current servlet mapping if applicable
        String rest = getPathWithinServletMapping(request, pathWithinApp);
        if (StringUtils.hasLength(rest)) {
            return rest;
        }
        else {
            return pathWithinApp;
        }
    }

In old version, the alwaysUseFullPath was false so rest was returned. The new version returns pathWithinApp which ignores index.html. In my opinion, this is incorrect.

The attached test app can be used to reproduce the bug. Follow these steps, 1. unzip webjar-welcome-bug.zip 2. cd webjar-welcome-bug 3. mvn package 4. Deploy app/target/test-app-1.0.war to Tomcat (9.0.40 was used). 5. Start Tomcat and try to access page http://localhost:8080/test-app-1.0/test/ and 404 will be returned. 6. If changing the Spring Boot version in app/pom.xml to 2.2.5.RELEASE and repeat step 3-5, the same request will return the content of index.html. webjar-welcome-bug.zip

Comment From: wilkinsona

Thanks for the report. UrlPathHelper and SimpleUrlHandlerMapping are part of Spring Framework. While you were tracing down the problem, did you identify something in Spring Boot that is causing Spring Framework to behave incorrectly or did you intend to report this to the Spring Framework team?

Comment From: zzcoder

I think it's a Spring Boot problem since we don't have this issue with applications that use Spring only.

I just did more investigation. The root cause is in WebMvcAutoConfiguration,

        public void configurePathMatch(PathMatchConfigurer configurer) {
            if (this.mvcProperties.getPathmatch()
                    .getMatchingStrategy() == WebMvcProperties.MatchingStrategy.PATH_PATTERN_PARSER) {
                configurer.setPatternParser(new PathPatternParser());
            }
            configurer.setUseSuffixPatternMatch(this.mvcProperties.getPathmatch().isUseSuffixPattern());
            configurer.setUseRegisteredSuffixPatternMatch(
                    this.mvcProperties.getPathmatch().isUseRegisteredSuffixPattern());
            this.dispatcherServletPath.ifAvailable((dispatcherPath) -> {
                String servletUrlMapping = dispatcherPath.getServletUrlMapping();
                if (servletUrlMapping.equals("/") && singleDispatcherServlet()) {
                    UrlPathHelper urlPathHelper = new UrlPathHelper();
                    urlPathHelper.setAlwaysUseFullPath(true);
                    configurer.setUrlPathHelper(urlPathHelper);
                }
            });
        }

This call urlPathHelper.setAlwaysUseFullPath(true) is new and it causes the incorrect behavior.

Comment From: wilkinsona

Thanks for the additional details.

I'm a bit surprised that this has ever worked. You are relying on Tomcat finding the index.html file (hence the use of a jar with the index file in META-INF/resources/) so that it appends index.html to the request, and then relying on Spring MVC's static resource handling being able to serve that file. Those split responsibilities don't feel quite right to me.

I'm not sure that this is something that we should attempt to fix in Spring Boot as the same situation could arise in a pure Spring MVC app. @bclozel will have a better idea than I do of the right thing to do here, but I think if a change is to be made anywhere, it should possibly be made in Framework.

In the meantime, you can avoid the problem by overriding the configuration of the URL path helper:

@Bean
@Order(1)
public WebMvcConfigurer webMvcConfigurer() {
    return new WebMvcConfigurer() {

        @Override
        public void configurePathMatch(PathMatchConfigurer configurer) {
            UrlPathHelper urlPathHelper = new UrlPathHelper();
            urlPathHelper.setAlwaysUseFullPath(false);
            configurer.setUrlPathHelper(urlPathHelper);
        }

    };

}

Comment From: zzcoder

Relying on web server to find the index.html is actually a common practice. AngularJS's default routing drops index.html.

I implemented work-around as suggested.

This is a change of behavior from old version, probably unintended so I report it as a bug. I upgraded Sprint Boot for a security vulnerability and all our web pages started to return 404. It took me a few days to find the root cause. If you all decide this is the correct behavior, I already have fix in place so I don't really care. Feel free to close the ticket.

I haven't tested this but I suspect this change might break Tomcat's Rewrite-Valve also. It uses the same mechanism to rewrite the URI.

Comment From: erikkemperman

Hi,

I believe I ran into the same problem -- after upgrading spring boot in my application (having been more or less forced to by various security notices on spring framework) things appeared to be working well in standalone mode (with embedded tomcat).

However when deploying the war to an external tomcat I am seeing exactly what is described above, requests to / no longer display the welcome page. I've tried to implement the suggested workaround, but it isn't making any difference. Perhaps I am doing it wrong?

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.util.UrlPathHelper;



@Configuration
public class FixWelcomePage {

    private final Logger log = LoggerFactory.getLogger(LoggingConfiguration.class);

    @Bean
    @Order(1)
    public WebMvcConfigurer webMvcConfigurer() {
        log.info("Fix welcome page 1");
        return new WebMvcConfigurer() {

            @Override
            public void configurePathMatch(PathMatchConfigurer configurer) {
                log.info("Fix welcome page 2");
                UrlPathHelper urlPathHelper = new UrlPathHelper();
                urlPathHelper.setAlwaysUseFullPath(false);
                configurer.setUrlPathHelper(urlPathHelper);
            }

        };

    }
}

For what it's worth, I am seeing both log lines printed as I deploy and start the app in tomcat's manager UI.

Any suggestions would be greatly appreciated, I honestly have no clue what else I can try. Downgrading Spring Boot to a version that did not have this bug is not really an option, as we'll have to deploy this on a public-facing server.

Comment From: wilkinsona

@erikkemperman It's hard to say if you're doing it wrong with an incomplete picture of what you've done. If you would like us to spend some time investigating, please take the time to provide a complete yet minimal sample that reproduces the problem you're seeing.

Comment From: erikkemperman

@wilkinsona Thanks for the quick response, and that's fair, of course!

I'm stuck with legacy code base that I don't know enough about, to be completely honest. I'm trying to keep the dependencies up to date but I'm probably not going to be able to make a minimal sample. My apologies.

For what it's worth, the story is that we had a spring boot app that worked both with embedded tomcat as well as with external tomcat, and after I upgraded to spring core and spring boot the embedded scenario worked as before but the external tomcat did not -- it is not showing the welcome page for requests to /

So then I found this issue and it seems a match, but my attempt at implementing the proposed workaround did not resolve the problem for me. In the mean time I was able to sidestep the issue by explicitly forwarding / to /index.html (and compensating for it in the frontend).