[JAVA] Resource handler settings when delivering SPA with the static resource function of Spring Boot

The static resource distribution function of SpringBoot is a very convenient function, but you can not access it except for the top page unless you specify the file name up to the extension. A general SPA has a routing function that uses the HTML5 History API, and while multiple URLs are assigned to each page, the only HTML file to prepare is index.html. For this reason, if the SPA is delivered as a static resource with the default settings of Spring Boot, the page cannot be displayed correctly when directly accessing other than the top page or when the screen is refreshed.

What you want to achieve

We will consider a setting method that meets the following requirements.

--The target page is displayed even if you access the top page directly. --The original page should be displayed even when the screen is refreshed. --For access to js and css files, return 404 if the target file does not exist.

The presence or absence of the extension is used to determine whether the access target is a page or a file. Access to a URL without an extension is considered to be a page access and returns index.html.

Workaround 1: Use hash routing on the SPA side

The easiest way is to use hash mode in the router settings on the SPA side (the following is an example of Nuxt.js).

js:nuxt.config.js


module.exports = {
  mode: 'spa',
  router: {
    mode: 'hash'
  },
  ......

Regardless of the routing on the SPA side, the request to the server will always be the access to the top page. With the default settings of SpringBoot, index.html is returned as a response as WelcomePage, so all page URLs are valid.

Workaround 2: Set Spring Boot request mapping

If you don't want to give up history mode, you need to configure it on the Spring Boot side.

About delivery of static resources with default settings

First of all, let's check how the static resource distribution of Spring Boot is set by default.

The default setting is WebMvcAutoConfiguration.java of the spring-boot-autoconfigure module. -It is described in (autoconfigure / src / main / java / org / springframework / boot / autoconfigure / web / module / WebMvcAutoConfiguration.java). In this class, the mapping for the following static resources is set.

If you set the log level of ʻorg.springframework.web` to DEBUG, you can confirm that it is set as follows in the log output at startup.

app.log


2018-06-05 23:17:29.654  INFO 18345 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-06-05 23:17:29.655  INFO 18345 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-06-05 23:17:29.732  INFO 18345 --- [           main] o.s.b.a.w.s.WelcomePageHandlerMapping    : Adding welcome page: class path resource [static/index.html]
2018-06-05 23:17:29.915 DEBUG 18345 --- [           main] o.s.w.s.resource.ResourceUrlProvider     : Looking for resource handler mappings
2018-06-05 23:17:29.916 DEBUG 18345 --- [           main] o.s.w.s.resource.ResourceUrlProvider     : Found resource handler mapping: URL pattern="/**/favicon.ico", locations=[class path resource [META-INF/resources/], class path resource [resources/], class path resource [static/], class path resource [public/], ServletContext resource [/], class path resource []], resolvers=[org.springframework.web.servlet.resource.PathResourceResolver@3b4ef7]
2018-06-05 23:17:29.916 DEBUG 18345 --- [           main] o.s.w.s.resource.ResourceUrlProvider     : Found resource handler mapping: URL pattern="/webjars/**", locations=[class path resource [META-INF/resources/webjars/]], resolvers=[org.springframework.web.servlet.resource.PathResourceResolver@1af05b03]
2018-06-05 23:17:29.916 DEBUG 18345 --- [           main] o.s.w.s.resource.ResourceUrlProvider     : Found resource handler mapping: URL pattern="/**", locations=[class path resource [META-INF/resources/], class path resource [resources/], class path resource [static/], class path resource [public/], ServletContext resource [/]], resolvers=[org.springframework.web.servlet.resource.PathResourceResolver@5987e932]

Setting method

Change the static resource mapping pattern

Add a setting in application.yml to return static resources only for paths with extensions. The default mapping pattern to static resources is defined in spring.mvc.static-path-pattern and will be overridden.

application.yml


spring:
  mvc:
    static-path-pattern: /**/*.*

Add a resource handler for the page URL

With the above settings, access to the page URL will deviate from the default static resource handler mapping settings. Therefore, define the following Configuration with reference to WebMvcAutoConfiguration.java so that index.html is returned when the target resource does not exist for the access to the page URL.

Html5HistoryModeResourceConfig.java


@Configuration
public class Html5HistoryModeResourceConfig implements WebMvcConfigurer {

    @Autowired
    private ResourceProperties resourceProperties;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry
            .addResourceHandler("/**")
            .addResourceLocations(resourceProperties.getStaticLocations())
            .resourceChain(false)
            .addResolver(new SpaPageResourceResolver());
    }

    public static class SpaPageResourceResolver extends PathResourceResolver {
        @Override
        protected Resource getResource(String resourcePath, Resource location) throws IOException {
            Resource resource = super.getResource(resourcePath, location);
            return resource != null ? resource : super.getResource("index.html", location);
        }
    }
}

Since the default settings take precedence, we have set resource handlers for all other paths. The storage path of static resources is obtained from the instance of ResourceProperties so that the setting of spring.resources.static-locations can be used as it is. The last SpaPageResourceResolver registered in ʻaddResolver` returns the response of index.html of the root path.

If the above Configuration class is loaded at startup, it should appear in the startup log as shown below.

app.log


2018-06-05 23:42:22.481  INFO 18576 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-06-05 23:42:22.481  INFO 18576 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/*.*] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-06-05 23:42:22.481  INFO 18576 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2018-06-05 23:42:22.781 DEBUG 18576 --- [           main] o.s.w.s.resource.ResourceUrlProvider     : Looking for resource handler mappings
2018-06-05 23:42:22.782 DEBUG 18576 --- [           main] o.s.w.s.resource.ResourceUrlProvider     : Found resource handler mapping: URL pattern="/**/favicon.ico", locations=[class path resource [META-INF/resources/], class path resource [resources/], class path resource [static/], class path resource [public/], ServletContext resource [/], class path resource []], resolvers=[org.springframework.web.servlet.resource.PathResourceResolver@1af1347d]
2018-06-05 23:42:22.782 DEBUG 18576 --- [           main] o.s.w.s.resource.ResourceUrlProvider     : Found resource handler mapping: URL pattern="/webjars/**", locations=[class path resource [META-INF/resources/webjars/]], resolvers=[org.springframework.web.servlet.resource.PathResourceResolver@632aa1a3]
2018-06-05 23:42:22.782 DEBUG 18576 --- [           main] o.s.w.s.resource.ResourceUrlProvider     : Found resource handler mapping: URL pattern="/**/*.*", locations=[class path resource [META-INF/resources/], class path resource [resources/], class path resource [static/], class path resource [public/], ServletContext resource [/]], resolvers=[org.springframework.web.servlet.resource.PathResourceResolver@20765ed5]
2018-06-05 23:42:22.782 DEBUG 18576 --- [           main] o.s.w.s.resource.ResourceUrlProvider     : Found resource handler mapping: URL pattern="/**", locations=[class path resource [META-INF/resources/], class path resource [resources/], class path resource [static/], class path resource [public/]], resolvers=[com.test.spa.Html5HistoryModeResourceConfig$SpaPageResourceResolver@3b582111]

Summary

Regarding the deployment of SPA, I think that there is another better way considering full-scale operation and SSR, but if you start the jar file alone like Jenkins, the screen will come with it. If you want to make it, I think this setting is effective.

Reference link

Spring Boot Reference Guide - 27.1.5 Static Content https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-developing-web-applications.html#boot-features-spring-mvc-static-content

Understand access to static resources on Spring MVC (+ Spring Boot)-Qiita https://qiita.com/kazuki43zoo/items/e12a72d4ac4de418ee37

Goslings Development Memo-Part 5: Spring Boot Final (Static Resource Processing) | To Be Decided https://www.kaitoy.xyz/2017/01/24/goslings-development-memo5-spring-boot-static-resources / /

html5 - Spring Boot with AngularJS html5Mode - Stack Overflow https://stackoverflow.com/questions/24837715/spring-boot-with-angularjs-html5mode

spring - Springboot/Angular2 - How to handle HTML5 urls? - Stack Overflow https://stackoverflow.com/questions/38516667/springboot-angular2-how-to-handle-html5-urls

Recommended Posts

Resource handler settings when delivering SPA with the static resource function of Spring Boot
Specify the encoding of static resources in Spring Boot
Access the built-in h2db of spring boot with jdbcTemplate
About the function of Spring Boot due to different versions
A story packed with the basics of Spring Boot (solved)
Memorandum of understanding when Spring Boot 1.5.10 → Spring Boot 2.0.0
See the behavior of entity update with Spring Boot + Spring Data JPA
How to access Socket directly with the TCP function of Spring Integration
Implement paging function with Spring Boot + Thymeleaf
Introducing the Spring Boot Actuator, a function that makes the operation of Spring Boot applications easier.
Organize the differences in behavior of @NotBlank, @NotEmpty, @NotNull with Spring Boot + Thymeleaf
The story of raising Spring Boot 1.5 series to 2.1 series
Let's check the feel of Spring Boot + Swagger 2.0
When @Transactional of Spring Boot does not work
[Introduction to Spring Boot] Authentication function with Spring Security
[Spring Boot] The story that the bean of the class with ConfigurationProperties annotation was not found
Error handling when the maximum file size is exceeded when uploading a file with Spring Boot
Settings for connecting to MySQL with Spring Boot + Spring JDBC
[Note] Configuration file when using Logback with Spring Boot
Spring with Kotorin --3. Omitting curly braces from the function
I tried to reduce the capacity of Spring Boot
Spring Boot with Spring Security Filter settings and addictive points
Create Restapi with Spring Boot ((1) Until Run of App)
A review note of the Spring Framework Resource interface
How to boot by environment with Spring Boot of Maven
Control the processing flow of Spring Batch with JavaConfig.
Download with Spring Boot
Change the injection target for each environment with Spring Boot 2
Extract SQL to property file with jdbcTemplate of spring boot
Try hitting the zip code search API with Spring Boot
Specify the character code of the source when building with Maven
Spring Boot application that specifies DB connection settings with parameters
Introduction of library ff4j that realizes FeatureToggle with Spring Boot
A story that made me regret when a "NotReadablePropertyException" occurred during the development of the Spring Boot application.