I have an spring boot app, which contains an angular front
like this:
src/main/resources/static/zanori2
Where in zanori2 I have the result of ng build
some like:
index.html, index.js, favico.ico and so on
I tried this resourceHandle:
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
/*@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {*/
//registry.addResourceHandler("/**/*")
/*.addResourceLocations("classpath:/static/zanori2/")
.resourceChain(true)
.addResolver(new PathResourceResolver() {
@Override
protected Resource getResource(String resourcePath,
Resource location) throws IOException {
Resource requestedResource = location.createRelative(resourcePath);
return requestedResource.exists() && requestedResource.isReadable() ? requestedResource
: new ClassPathResource("/static/zanori2/index.html");
}
});
}
}
However when I go to: localhost:8080/zanori2/index.html
it return me to localhost:8080
and the js files works.
However it is weird because I am not allowed to share the url because if I go directly to localhost:8080 I get a not found page.
And with this other configuration:
@Configuration
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private Environment env;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
/* Caching strategy */
boolean prodMode = Arrays.asList(env.getActiveProfiles()).contains("pro");
Integer cachePeriod = prodMode ? null : 0;
boolean useResourceCache = prodMode;
VersionResourceResolver versionResourceResolver = new VersionResourceResolver();
versionResourceResolver.addContentVersionStrategy("/**/*.js", "/**/*.css");
AppCacheManifestTransformer transformer = new AppCacheManifestTransformer();
/* robots.txt */
registry.addResourceHandler("/robots.txt")
.addResourceLocations("classpath:/static/robots.txt");
/* All other resources */
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/zanori2")
.setCachePeriod(cachePeriod)
.resourceChain(useResourceCache)
.addResolver(versionResourceResolver)
.addTransformer(transformer);
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
/* Make sure Thymeleaf views are not accessible directly as static resources */
registry.addRedirectViewController("/app/*.html", "/");
/* Default mapping */
registry.addRedirectViewController("/", "/app/index.html");
/* Application entry */
registry.addViewController("/app/index.html").setViewName("index");
}
}
I go to localhost:8080/zanori2/index.html
and I keep in the same url however my js files are not found so is not working too.
I do not found any example of this working properly.
Spring will automatically search in a number of places for paths which aren't matched by any controllers or other settings in the web config. These locations are currently checked by default:
classpath:/META-INF/resources/
classpath:/resources/
classpath:/static/
classpath:/public/
You can override this behaviour by setting the
spring.web.resources.static-locations
application property.
Starting from a clean project and adding:
spring.web.resources.static-locations=classpath:/static/zanori2/
to your application.properties
will make Spring search the intended path for static resources.
/
Remember in all of the below that the mapping to static assets has already been changed by the above so in the below, the path /a/b
will REALLY fetch /static/zanori2/a/b
. Also remember that a Spring controller will always have precedence over static assets so if you define a Spring controller which interferes with a static path, it will be used instead.
If you also support a single-page frontend with internal routing, you need to add some configuration to the WebMvcConfigurer
. The trick is to still load all static content from its real location but to forward all the paths inside the single-page app to /
. Assuming that paths inside the app will never have a file suffix preceded by a period (.), which typically all real static files from the backend have, this can be done by some additionss to your WebMvcConfigurer
.
The additions depend on which kind of pattern matching is used in SpringMVC.
With path pattern matching (which is the new default in up-to-date Spring Boot):
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry
.addViewController("/{path1:[\\w]+}")
.setViewName("forward:/")
registry
.addViewController("/{path1}/{path2:[\\w]+}")
.setViewName("forward:/")
registry
.addViewController("/{path1}/{path2}/{path3:[\\w]+}")
.setViewName("forward:/")
}
With path pattern matching, there is no easy way to do this for an arbitrary number of path levels so you have to add one of these statements for every level of nesting your frontend internal paths should support. This is because path patterns only allow matching of multiple path levels (**
) at the end of a pattern.
The above supports up to three levels so when directly entering in the browser address bar:
/
/* / is served */
/a
/* / is served which can then route internally to /a */
/a/b
/* / is served which can then route internally to /a/b */
/a/b/c
/* / is served which can then route internally to /a/b/c */
/a/b/c/d
/* will NOT work, tries to serve actual /a/b/c/d */
/a/b/c.txt
/* / will NOT work, tries to serve actual /a/b/c.txt since contains a period */
What's going on here? As said, these are path patterns which you can read about here:
{pathX}
matches a path segment and stores the path in the variable pathX
(which we don't care about).With ant pattern matching, which used to be the default, similar rules apply but with some differences. Path pattern matching is generally more potent but as previously said, it does not allow you to match an arbitrary number of path levels (**
) at no other place then at the end.
With ant pattern matching, the previous config can be simplified into
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry
.addViewController("/**/{path:[\\w]+}")
.setViewName("forward:/")
}
Now, we match an arbitrary number of path levels before the last path, yielding the following results:
/
/* / is served */
/a
/* / is served which can then route internally to /a */
/a/b
/* / is served which can then route internally to /a/b */
/a/b/c
/* / is served which can then route internally to /a/b/c */
/a/b/c/d
/* / is served which can then route internally to /a/b/c/d */
/a/b/c.txt
/* / will NOT work, tries to serve actual /a/b/c.txt since contains a period */
So depending on what you need in the rest of your app, I would choose one of these solutions, preferably the one with ant patterns.