4

I need a Spring @RequestMapping that I will use to match all non-/api/... paths like /products to forward them to my / (by return "forward:/";) view controller which actually serves index.html contents and causes frontend (React) application to be served so that frontend application can handle this path to render a page.

Please note, that implicitly this @RequestMapping cannot match / as it would end up in infinite recurence and StackOverflowError. It may be worth to exclude index.html also.

In other words, I want my /api/products to be handled "locally" by Spring Boot application, where /products/ should be forwarded to render React app. There is not Apache or Nginx proxy - React app is served by Spring thanks to static resource mappings.

I have studied many similar Stackoverflow questions in this area and none of them is working.

Important note: I saw cases on Stackoverflow where the problem only supposed to be resolved, just because initially it looked like working (/products/ forwarded and /api/products handled by API Controller), but it actually wasn't, because proposed regex was matching everything and /api/products were handled only by Spring mapping precedences (more specific "wins"), but the same path would actually match the pattern if no other Controller would exist.

This results in inappropriate 404 (or mapping not found) error handling - calling any, even not existing /api/something endpoint ends up with forward to index.html in all answers that I have found, which I want to avoid. Accessing not existing /api/something endpoint should rather end up with no handler found Spring error, not in forward. An example of such solution, is the most popular one, like: @RequestMapping(value = "{_:^(?!api).*$}")

The problem is, whatever pattern I try, it ends up with forwarding all of my test cases (like /api /api/ /api/x /page/ /page/2 or none.

Just some examples of patterns I have tried:

@RequestMapping(value = "{_:^(?!index\\.html|api).*$}")
@RequestMapping(value = "{x:^(?!api).*$}") 
@RequestMapping(value = "/{path:^(?!api/).*$}")
@RequestMapping(value = "/{dataType:^.*(?!api).*$}")
@RequestMapping(value = "/{arg:(?!sitemap.xml|api|index.html).*$}")
@RequestMapping(value = "{arg:^(?!api|!index.html).*$}/**")
@RequestMapping(value = "{_:^(?!index\\.html|api).*$}")
Piotr Müller
  • 5,323
  • 5
  • 55
  • 82

2 Answers2

1

Looks like the original regex actually works (e.g. "{_:^(?!index\\.html|api).*$}").

The problem is, Jetty, to display 404 was internally forwarding to /error page which also was subject for that controller mapping! Effectively forwarding to frontend app from public resources instead of rendering error page

Piotr Müller
  • 5,323
  • 5
  • 55
  • 82
0

Question still valid, but for people that need solution I can propose to forward on filter level:

 @Bean
    public FilterRegistrationBean nonApiRequestToRootPathForwarderFilterRegistrationbean() {
        FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
        filterFilterRegistrationBean.setFilter(new Filter() {
            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
                HttpServletRequest request1 = (HttpServletRequest) request;
                if (!request1.getRequestURI().startsWith("/api/") && !request1.getRequestURI().equals("/")) {
                    RequestDispatcher requestDispatcher = request.getRequestDispatcher("/");
                    requestDispatcher.forward(request, response);
                    return;
                }

                chain.doFilter(request, response);
            }
        });
        return filterFilterRegistrationBean;
    }
Piotr Müller
  • 5,323
  • 5
  • 55
  • 82