My question is very similar to this question that has already been asked and answered but is not 100% up-to-date.
We used the solution from Chris Gaskill for quite some time and it suited us perfectly because we wanted to redirect requests that contain more than one path segment (i.e. /foo/bar)
From Spring Boot 2.4 on, Boot uses the PathPatternParser instead of the AntPathMatcher, wherein the former does not support ** at the start of a pattern anymore (see docs).
Is there some other solution to get the same behavior? What do you use to redirect all requests, that did not match anything else, to the index.html of the Angular app?
This is the code of the controller that forwards the requests.
@Controller
class SpaRoutingController {
@GetMapping("/**/{path:[^\\.]*}", headers = "X-Requested-With!=XMLHttpRequest")
fun forward(): String? {
return "forward:/"
}
}
I would rather go solution where I configure a WebMvcConfigurer instead. Please see below for code.
Maven Configuration
Maven configuration directs frontend-maven-plugin to build angular and copy built content into target/static directory, so that it is available in final WAR and JAR built. Then I additionally configure a WebMvcConfigurer to find a resource in static directory of JAR / WAR, if found - send it to client, otherwise redirect to index.html.
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>${maven-clean-plugin.version}</version>
<configuration>
<filesets>
<fileset>
<directory>src/main/webapp</directory>
<includes>
<include>*.js</include>
<include>*.txt</include>
<include>*.html</include>
<include>*.css</include>
<include>*.map</include>
<include>*.json</include>
<include>*.ico</include>
<followSymlinks>false</followSymlinks>
</includes>
</fileset>
<fileset>
<directory>src/main/webapp/assets</directory>
<includes>
<include>**</include>
<followSymlinks>false</followSymlinks>
</includes>
</fileset>
</filesets>
</configuration>
<executions>
<execution>
<id>auto-clean</id>
<phase>initialize</phase>
<goals>
<goal>clean</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<phase>process-resources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/classes/static</outputDirectory>
<includeEmptyDirs>true</includeEmptyDirs>
<resources>
<resource>
<directory>${basedir}/src/main/${angular.project.name}/dist/${angular.project.name}</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>com.github.eirslett</groupId>
<artifactId>frontend-maven-plugin</artifactId>
<version>1.8.0</version>
<configuration>
<workingDirectory>src/main/${angular.project.name}</workingDirectory>
</configuration>
<executions>
<execution>
<id>install node and npm</id>
<goals>
<goal>install-node-and-npm</goal>
</goals>
<phase>generate-resources</phase>
<configuration>
<nodeVersion>v14.16.1</nodeVersion>
</configuration>
</execution>
<execution>
<id>npm build angular</id>
<goals>
<goal>npm</goal>
</goals>
<phase>generate-resources</phase>
<configuration>
<arguments>run build:prod</arguments>
</configuration>
</execution>
</executions>
</plugin>
MVC Configuration
Here you try to find if a resource exists in your static directory, if not redirect it to index.html.
You may have to change source location as per your requirement.
@Configuration
public class ApplicationWebMvcConfiguration implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/")
.resourceChain(true)
.addResolver(new PathResourceResolver() {
@Override
protected Resource getResource(String resourcePath, Resource location)
throws IOException {
Resource requestedResource = location.createRelative(resourcePath);
if (requestedResource.exists() && requestedResource.isReadable()) {
return requestedResource;
}
return new ClassPathResource("/static/index.html");
}
});
}
}
You should add extra Controller for corner case for path / or ""
@Controller
public class StaticContentController {
private final ResourceLoader resourceLoader;
public StaticContentController(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
@GetMapping(value = "/", produces = MediaType.TEXT_HTML_VALUE)
@ResponseBody
public Resource serveStaticContent() {
// Load and return your static HTML file
return resourceLoader.getResource("classpath:/static/index.html");
}
}
PathPatternParser equivalent to previous AntPathMatcher /** would be:
@GetMapping("/{*path}")
according to spring documentation:
/resources/{*path} — matches all files underneath the /resources/, as well as /resources, and captures their relative path in a variable named "path"; /resources/image.png will match with "path" → "/image.png", and /resources/css/spring.css will match with "path" → "/css/spring.css"
Edit - relating to loop:
in your case in order to avoid the loop due to the use of forward:/ if you only care about loading your angular page just replace forward with the actual content of your index.html
if for some reason you need to forward in order to flatten the uri before returning the response you can solve it in various manners
you can either forward:/index.html @GetMapping("/index.html") which should have precedence over wildcard or use @PathVariable in order to differentiate behavior and avoid eternal forward loop
@Controller
class SpaRoutingController {
@GetMapping("/{*path}")
fun forward(@PathVariable(value="path", required=false) path: String): String? {
if ("/".equals(path)) { // may need to handle additional cases
return ""; // or your index.html
}
return "forward:/";
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With