Spring Boot does not serve a static resource

22.5k views Asked by At

Spring Boot does not serve a static resource if there is a method in the controller with a suitable path template. I am currently reading the book "Spring Boot in Action" (2015). There is an example project in the second chapter.

The project has the following structure:

.
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── haven
    │   │       ├── Book.java
    │   │       ├── ReadingListApplication.java
    │   │       ├── ReadingListController.java
    │   │       └── ReadingListRepository.java
    │   └── resources
    │       ├── application.properties
    │       ├── static
    │       │   └── style.css
    │       └── templates
    │           └── readingList.html
    └── test
        └── java
            └── haven
                └── ReadingListApplicationTests.java

And uses the following dependencies:

org.springframework.boot:spring-boot-starter-data-jpa
org.springframework.boot:spring-boot-starter-thymeleaf
org.springframework.boot:spring-boot-starter-web
org.springframework.boot:spring-boot-starter-test
com.h2database:h2

I use 2.4.5 spring boot version.

src/main/resources/templates/readingList.html header:

...
<head>
   <title>Reading List</title>
   <link rel="stylesheet" th:href="@{/style.css}"></link>
</head>
...

src/main/java/haven/ReadingListController.java :

package haven;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping(value = "/")
public class ReadingListController {
    private ReadingListRepository readingListRepository;

    @Autowired
    public ReadingListController(ReadingListRepository readingListRepository) {
        this.readingListRepository = readingListRepository;
    }
    
    @RequestMapping(value = "/{reader}", method = RequestMethod.GET)
    public String readersBooks(@PathVariable(value = "reader") String reader,
                               Model model) {
        
        List<Book> readingList = readingListRepository.findByReader(reader);
        if(readingList != null) {
            model.addAttribute("books", readingList);   
        }
        System.out.println("reader name is " + reader);
        return "readingList";
    }
    
    @RequestMapping(value = "/{reader}", method = RequestMethod.POST)
    public String addToReaderList(@PathVariable(value = "reader") String reader,
                                  Book book) {
        book.setReader(reader);
        readingListRepository.save(book);
        return "redirect:{reader}";
    }
}

If I make a GET request like /andrew, the readersBooks method will work thrice. First for /andrew, then for /style.css, and then for /favicon.ico.

If I change both paths, for example like this

@RequestMapping(value = "/reader/{reader}", method = RequestMethod.GET)
@RequestMapping(value = "/reader/{reader}", method = RequestMethod.POST)

or remove both methods altogether, then the resources are retrieved as expected. (Here I was a little surprised why changing the path for GET was not enough. I started getting 405 error for style.css and favicon.icon. .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported])

This answer helped me. I added the following lines to the application.properties:

spring.web.resources.chain.strategy.fixed.enabled=true
spring.web.resources.chain.strategy.fixed.paths=/**
spring.web.resources.chain.strategy.fixed.version=v1

Why are the default properties not enough? I've looked at several tutorials and everyone says that just put static resources in one of the paths and everything will be fine. The code from the book, which works for the author, does not work exactly the same for me. Maybe something has changed? Book of 2015. And what do these three properties actually do?

UPDATE

I added spring security to the project like in this guide.

org.springframework.boot:spring-boot-starter-security

From this point on, there are three groups:

  • / and /home - available to everyone and always
  • /login - for authentication, available to everyone and always too
  • /{reader} - for authorized users

Added two classes:
src/main/java/haven/MvcConfig.java:

package haven;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("home");
        registry.addViewController("/home").setViewName("home");
        registry.addViewController("/login").setViewName("login");
    }
}

src/main/java/haven/WebSecurityConfig.java:

package haven;

import java.util.function.Function;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/", "/home").permitAll()
                .anyRequest().authenticated()
            .and()
                .formLogin().loginPage("/login").permitAll()
            .and()
                .logout().permitAll();
    }
    
    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        Function<String, String> encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder()::encode;
        UserDetails user = User.withUsername("user")
                               .password("password").passwordEncoder(encoder)
                               .roles("USER")
                               .build();
        
        return new InMemoryUserDetailsManager(user);
    }
}

src/main/resources/templates/home.html:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
    xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Spring Security Example</title>
    </head>
    <body>
        <h1>Welcome home!</h1>
    </body>
</html>

src/main/resources/templates/login.html:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="https://www.thymeleaf.org"
      xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Spring Security Example </title>
    </head>
    <body>
        <div th:if="${param.error}">
            Invalid username and password.
        </div>
        <div th:if="${param.logout}">
            You have been logged out.
        </div>
        <form th:action="@{/login}" method="post">
            <div><label> User Name : <input type="text" name="username"/> </label></div>
            <div><label> Password: <input type="password" name="password"/> </label></div>
            <div><input type="submit" value="Sign In"/></div>
        </form>
    </body>
</html>

When I make a request, for example /peter the server asks to authenticate, redirecting to /login. But instead of displaying the page login.html from the resource, it gives this /login to controller. The controller gives the page readingList.html to the user with reader name "login". Moreover, this page is trying to pull up resources style.css and favicon.ico. But of course the user is still not authenticated and spring security redirects two more times to the /login.

In other words, before adding spring security, the console output was:

reader name is peter
reader name is style.css
reader name is favicon.ico

but now:

reader name is login
reader name is login
reader name is login

Of course I expect to see only

reader name is peter

and only after authorization, if spring security is added.

It looks like the controller takes precedence over the resource. Even after the web security! But if I understand Craig Walls correctly, it should be exactly the opposite. Otherwise, there is no way to get the resource, having a request pattern /{reader}.

Maybe this will help.
Earlier when I used web.xml (in another project) I had a similar problem with servlet mapping, with url pattern /*:

<servlet-mapping>
    <servlet-name>spring</servlet-name>
    <url-pattern>/*</url-pattern>
</servlet-mapping>

Without this pattern, resources are pulled up normally without any additional information. But if you add it, then resource requests like /style.css or /script.js or /image.png are sent to the controller for processing instead of just returning this resource. Then I thought I didn't understand something. I had two ways, use something like <url-pattern>/blablabla/*</url-pattern> (without putting resources in the "blablabla" folder of course) or exact match for any request (fortunately, there were only a finite number of them). Now I see it again, but in spring boot project...

UPDATE

As @PiotrP.Karwasz advised, I tried to change the priorities as indicated here. It had consequences, but did not solve the problem. I created a new project without spring security. Right now my project struture looks like this:

.
├── pom.xml
└── src
    └── main
        ├── java
        │   └── haven
        │       ├── Book.java
        │       ├── MvcConfig.java
        │       ├── ReadingListApplication.java
        │       ├── ReadingListController.java
        │       └── ReadingListRepository.java
        └── resources
            ├── application.properties
            ├── static
            │   ├── favicon.ico
            │   ├── page.html
            │   └── style.css
            └── templates
                ├── home.html
                └── reading-list.html

src/main/java/haven/MvcConfig.java:

package haven;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.resource.ResourceUrlProvider;

@Configuration
public class MvcConfig extends WebMvcConfigurationSupport {
    
    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
        registry.setOrder(0);
    }
    
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("home");
        registry.addViewController("/home").setViewName("home");
        registry.setOrder(1);
    }
    
    @Override
    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping(
            ContentNegotiationManager contentNegotiationManager,
            FormattingConversionService conversionService,
            ResourceUrlProvider resourceUrlProvider) {
        RequestMappingHandlerMapping handler = super.requestMappingHandlerMapping(
                                                        contentNegotiationManager,
                                                        conversionService,
                                                        resourceUrlProvider);
        handler.setOrder(2);
        return handler;
    }
}

There is no longer a requestMappingHandlerMapping method with no arguments. I am guessing that the new method might work in a similar way. Maybe I'm wrong.

It's strange that default priorities are:

ResourceHandlerRegistry: Integer.MAX_VALUE - 1 = Ordered.LOWEST_PRECEDENCE - 1 = 2147483646
ViewControllerRegistry: 1
RequestMappingHandlerMapping: 0

RequestMappingHandlerMapping does not have field order, but in super class AbstractHandlerMapping there is

private int order = Ordered.LOWEST_PRECEDENCE;  // default: same as non-Ordered

It is 2147483647. Anyway, the method requestMappingHandlerMapping in WebMvcConfigurationSupport class has a line

mapping.setOrder(0);

which prioritizes it higher than ResourceHandlerRegistry.
So, indeed, resources have a very low priority by default. But shouldn't it be the other way around?
However, from now on I can get resources, but any other requests will always return a 404 response. Even for / or /home. But if I set the priority of the ViewControllerRegistry to be greater than or equal to the ResourceHandlerRegistry, then / and /home requests will work too, but not all others. For example:

ResourceHandlerRegistry: 0
ViewControllerRegistry: 0 (or even -1!)
RequestMappingHandlerMapping: 1

If I set the priority of the RequestMappingHandlerMapping higher or equal to the others leads to the original problem when the method readersBooks handled any request. There is an exception: if I make a request /style.css the header will contain Content-Type: text/css;charset=UTF-8, but body will as content of reading-list.html. To any other request, the response is content of the reading-list.html with Content-Type: text/html;charset=UTF-8, as it was originally before the priorities were changed.

I am a complete novice in this. Is there a simple solution to the problem? I am almost completely sure that even if somehow it is possible to set priorities correctly and process requests, as I tried to do it now, it will only be a "treating symptoms".

I have set priorities like this:

ResourceHandlerRegistry: 0
ViewControllerRegistry: 1
RequestMappingHandlerMapping: 2

Startup logs with logging.level.web=DEBUG:


  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v2.4.5)

2021-05-17 14:54:49.739  INFO 169307 --- [           main] haven.ReadingListApplication             : Starting ReadingListApplication using Java 1.8.0_292 on haven with PID 169307 (/home/vessel/Documents/eclipse-java-workspace/spring-boot-in-action/readinglist/target/classes started by vessel in /home/vessel/Documents/eclipse-java-workspace/spring-boot-in-action/readinglist)
2021-05-17 14:54:49.745  INFO 169307 --- [           main] haven.ReadingListApplication             : No active profile set, falling back to default profiles: default
2021-05-17 14:54:51.162  INFO 169307 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2021-05-17 14:54:51.278  INFO 169307 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 95 ms. Found 1 JPA repository interfaces.
2021-05-17 14:54:52.463  INFO 169307 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
2021-05-17 14:54:52.483  INFO 169307 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2021-05-17 14:54:52.483  INFO 169307 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.45]
2021-05-17 14:54:52.622  INFO 169307 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2021-05-17 14:54:52.622  INFO 169307 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 2718 ms
2021-05-17 14:54:52.655 DEBUG 169307 --- [           main] o.s.b.w.s.ServletContextInitializerBeans : Mapping filters: characterEncodingFilter urls=[/*] order=-2147483648
2021-05-17 14:54:52.656 DEBUG 169307 --- [           main] o.s.b.w.s.ServletContextInitializerBeans : Mapping servlets: dispatcherServlet urls=[/]
2021-05-17 14:54:52.986  INFO 169307 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2021-05-17 14:54:53.231  INFO 169307 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2021-05-17 14:54:53.324  INFO 169307 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2021-05-17 14:54:53.436  INFO 169307 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.4.30.Final
2021-05-17 14:54:53.762  INFO 169307 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2021-05-17 14:54:53.966  INFO 169307 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2021-05-17 14:54:55.122  INFO 169307 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2021-05-17 14:54:55.139  INFO 169307 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2021-05-17 14:54:55.904 DEBUG 169307 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : 4 mappings in 'requestMappingHandlerMapping'
2021-05-17 14:54:55.921 DEBUG 169307 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Patterns [/, /home] in 'viewControllerHandlerMapping'
2021-05-17 14:54:55.961 DEBUG 169307 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Patterns [/**] in 'resourceHandlerMapping'
2021-05-17 14:54:56.001 DEBUG 169307 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : ControllerAdvice beans: 0 @ModelAttribute, 0 @InitBinder, 1 RequestBodyAdvice, 1 ResponseBodyAdvice
2021-05-17 14:54:56.060 DEBUG 169307 --- [           main] .m.m.a.ExceptionHandlerExceptionResolver : ControllerAdvice beans: 0 @ExceptionHandler, 1 ResponseBodyAdvice
2021-05-17 14:54:56.135  WARN 169307 --- [           main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2021-05-17 14:54:56.483  INFO 169307 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2021-05-17 14:54:56.510  INFO 169307 --- [           main] haven.ReadingListApplication             : Started ReadingListApplication in 7.783 seconds (JVM running for 8.76)

First request is /style.css:

2021-05-17 14:55:26.594  INFO 169307 --- [nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2021-05-17 14:55:26.594  INFO 169307 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2021-05-17 14:55:26.595 DEBUG 169307 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Detected StandardServletMultipartResolver
2021-05-17 14:55:26.595 DEBUG 169307 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Detected AcceptHeaderLocaleResolver
2021-05-17 14:55:26.595 DEBUG 169307 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Detected FixedThemeResolver
2021-05-17 14:55:26.597 DEBUG 169307 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Detected org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator@7a65c995
2021-05-17 14:55:26.599 DEBUG 169307 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Detected org.springframework.web.servlet.support.SessionFlashMapManager@4be490da
2021-05-17 14:55:26.600 DEBUG 169307 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
2021-05-17 14:55:26.600  INFO 169307 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Completed initialization in 5 ms
2021-05-17 14:55:26.626 DEBUG 169307 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : GET "/style.css", parameters={}
2021-05-17 14:55:26.642 DEBUG 169307 --- [nio-8080-exec-2] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped to ResourceHttpRequestHandler [Classpath [static/]]
2021-05-17 14:55:26.711 DEBUG 169307 --- [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet        : Completed 200 OK

Second request is /:

2021-05-17 14:56:06.057 DEBUG 169307 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet        : GET "/", parameters={}
2021-05-17 14:56:06.064 DEBUG 169307 --- [nio-8080-exec-3] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped to ResourceHttpRequestHandler [Classpath [static/]]
2021-05-17 14:56:06.069 DEBUG 169307 --- [nio-8080-exec-3] o.s.w.s.r.ResourceHttpRequestHandler     : Resource not found
2021-05-17 14:56:06.070 DEBUG 169307 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet        : Completed 404 NOT_FOUND
2021-05-17 14:56:06.085 DEBUG 169307 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet        : "ERROR" dispatch for GET "/error", parameters={}
2021-05-17 14:56:06.090 DEBUG 169307 --- [nio-8080-exec-3] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped to ResourceHttpRequestHandler [Classpath [static/]]
2021-05-17 14:56:06.092 DEBUG 169307 --- [nio-8080-exec-3] o.s.w.s.r.ResourceHttpRequestHandler     : Resource not found
2021-05-17 14:56:06.093 DEBUG 169307 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet        : Exiting from "ERROR" dispatch, status 404

Third request is: /home:

2021-05-17 14:57:42.016 DEBUG 169307 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet        : GET "/home", parameters={}
2021-05-17 14:57:42.017 DEBUG 169307 --- [nio-8080-exec-5] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped to ResourceHttpRequestHandler [Classpath [static/]]
2021-05-17 14:57:42.019 DEBUG 169307 --- [nio-8080-exec-5] o.s.w.s.r.ResourceHttpRequestHandler     : Resource not found
2021-05-17 14:57:42.022 DEBUG 169307 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet        : Completed 404 NOT_FOUND
2021-05-17 14:57:42.023 DEBUG 169307 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet        : "ERROR" dispatch for GET "/error", parameters={}
2021-05-17 14:57:42.026 DEBUG 169307 --- [nio-8080-exec-5] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped to ResourceHttpRequestHandler [Classpath [static/]]
2021-05-17 14:57:42.027 DEBUG 169307 --- [nio-8080-exec-5] o.s.w.s.r.ResourceHttpRequestHandler     : Resource not found
2021-05-17 14:57:42.030 DEBUG 169307 --- [nio-8080-exec-5] o.s.web.servlet.DispatcherServlet        : Exiting from "ERROR" dispatch, status 404

Fourth request is /andrew:

2021-05-17 14:58:30.045 DEBUG 169307 --- [nio-8080-exec-8] o.s.web.servlet.DispatcherServlet        : GET "/andrew", parameters={}
2021-05-17 14:58:30.046 DEBUG 169307 --- [nio-8080-exec-8] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped to ResourceHttpRequestHandler [Classpath [static/]]
2021-05-17 14:58:30.047 DEBUG 169307 --- [nio-8080-exec-8] o.s.w.s.r.ResourceHttpRequestHandler     : Resource not found
2021-05-17 14:58:30.048 DEBUG 169307 --- [nio-8080-exec-8] o.s.web.servlet.DispatcherServlet        : Completed 404 NOT_FOUND
2021-05-17 14:58:30.052 DEBUG 169307 --- [nio-8080-exec-8] o.s.web.servlet.DispatcherServlet        : "ERROR" dispatch for GET "/error", parameters={}
2021-05-17 14:58:30.053 DEBUG 169307 --- [nio-8080-exec-8] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped to ResourceHttpRequestHandler [Classpath [static/]]
2021-05-17 14:58:30.054 DEBUG 169307 --- [nio-8080-exec-8] o.s.w.s.r.ResourceHttpRequestHandler     : Resource not found
2021-05-17 14:58:30.059 DEBUG 169307 --- [nio-8080-exec-8] o.s.web.servlet.DispatcherServlet        : Exiting from "ERROR" dispatch, status 404

The second time I have set the priorities like this:

ResourceHandlerRegistry: 0
ViewControllerRegistry: 0
RequestMappingHandlerMapping: 1

Nothing has changed except the responses to the second and third requests. It seems that when a resource has the highest priority, it prevents others from handling requests.
Second request is /:

2021-05-17 15:00:43.815 DEBUG 169868 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet        : GET "/", parameters={}
2021-05-17 15:00:43.816 DEBUG 169868 --- [nio-8080-exec-3] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped to ParameterizableViewController [view="home"]
2021-05-17 15:00:44.345 DEBUG 169868 --- [nio-8080-exec-3] o.s.web.servlet.DispatcherServlet        : Completed 200 OK

Third request is: /home:

2021-05-17 15:01:41.909 DEBUG 169868 --- [nio-8080-exec-4] o.s.web.servlet.DispatcherServlet        : GET "/home", parameters={}
2021-05-17 15:01:41.912 DEBUG 169868 --- [nio-8080-exec-4] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped to ParameterizableViewController [view="home"]
2021-05-17 15:01:41.915 DEBUG 169868 --- [nio-8080-exec-4] o.s.web.servlet.DispatcherServlet        : Completed 200 OK

Do I need to show some other logs? Perhaps something was not configured properly initially?

1

There are 1 answers

0
gerard On

Spring Boot does not serve a static resource...Is there a simple solution to the problem?

Yes there is, basically the example on chapter two of the book "Spring Boot in Action" (2015) is just wrong.

In the code they included and the following explanation, the controller method's are annotated with @RequestMapping(value="/{reader}". This makes the methods match everything after / , set the path value into the reader variable and respond with the readingList.html template, this includes the calls to the static files too (/style.css, /script.js, /image.png) so the template can't access it's style sheet because such request will return the html template itself instead.

In the book pictures you can see they're using the application by navigating to /readingList/craig so from there you can figure out the real code to be used needs to be: @RequestMapping(value="/readingList/{reader}" correct path readingList/{reader} That way it will work as expected and not intercept all calls to the root directory from where spring serves the static files.