Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring Boot 2.6 and Angular in static Resources

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:/"
  }
}
like image 883
M4R1KU Avatar asked Jun 27 '26 10:06

M4R1KU


2 Answers

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");
    }
}
like image 115
Paras Avatar answered Jun 30 '26 10:06

Paras


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:/";
  }
}
like image 41
ezer Avatar answered Jun 30 '26 09:06

ezer



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!