1

I am facing an issue with my Spring Boot application where I have a single controller that handles POST requests only. I have set the context path in my application.yml as server.servlet.context-path: /context/path. The goal is to handle POST requests for both /context/path and /context/path/ URLs.

My controller looks like this:

@RestController
@RequestMapping("")
public class MyController {

    @PostMapping({ "", "/" })
    public ResponseEntity<String> handlePostRequest() {
        // Handling POST request logic
        return ResponseEntity.ok("POST request handled");
    }
}

When I send a POST request to /context/path, it gets redirected with a 302 status code and the request method changes to GET, and it gets redirected to /context/path/.

I have tried different combinations of @RequestMapping and PostMapping. Nothing worked.

I found some recommended solution to create a WebConfiguration and override the configurePathMatch method. But the methods like setUseTrailingSlashMatch or setMatchOptionalTrailingSeparator are deprecated.

Despite these attempts, the issue persists. How can I configure my application to handle the requests with or without trailing slashes? Any insights or suggestions on resolving this issue would be greatly appreciated.

Rafi
  • 467
  • 6
  • 17
  • If you don't have a prefix for the routes the controller handles just use `@RequestMapping` without explicit endpoint and let the `*Mapping` declare the routes. – Slevin Aug 14 '23 at 00:42

1 Answers1

0

The migration to Spring Boot 3 and thus respectively to Spring Framework 6 comes with major changes. One of them is that the configuration option for trailing slash matching has been declared deprecated

There are a few options to deal with this:

  • Use a redirect filter within the SecurityFilterChain
  • Explicitly declare all supported routes (with and w/o trailing slash) -> my personal preference
  • Use a request wrapper filter to internally follow the correct route w/o requesting a redirect
  • Use an URL rewrite filter at servlet container level (e.g. Tuckey)
  • Use an URL rewrite engine at server level (e.g. Apache mod_rewrite)

To hardcode somehow additionally the routes for trailing slashes as well is for the most applications not that hard. You only have to find your preferred way to get them into the endpoints declarations. Since most applications have 10 or so hard routes and almost all other routes are dynamically generated via PathVariables, managing this amount of endpoints ist quite possible.

However, if you wanna deal with this situation at application level anyhow, here a proper working redirect filter you can add to the SecurityFilterChain:

public class TrailingSlashRedirectFilter extends OncePerRequestFilter {

    private static final Logger logger = LoggerFactory.getLogger(TrailingSlashRedirectFilter.class);

    @Override
    protected void doFilterInternal(
            HttpServletRequest request,
            HttpServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {

        /* We want to obtain the complete request URL including the query string */
        String url = ServletUriComponentsBuilder.fromRequest(request).build().toUriString();
        String path = request.getRequestURI();
        String fixedUrl = "";

        if (url.endsWith("/") && path.length() > 1 /* not the root path */)
            fixedUrl = url.substring(0, url.length() - 1);

        if (path.isEmpty() /* root path without '/' */)
            fixedUrl = url + "/";

        if (!fixedUrl.isEmpty()) {
            response.setHeader(HttpHeaders.LOCATION, fixedUrl);
            response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
            logger.trace("Redirecting with HttpStatus 301 for requested URL '{}' to '{}'", url, fixedUrl);
        } else {
            filterChain.doFilter(request, response);
        }
    }
}

You then simply add it like that:

public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

    return http
        //...

        .addFilterBefore(new TrailingSlashRedirectFilter(), DisableEncodeUrlFilter.class)

        //...


        .build();
    }
Slevin
  • 617
  • 1
  • 7
  • 17
  • Explicitly declaring all supported routes (with and w/o trailing slash) is my personal preference as well. That's why I was trying it, but it didn't seem to work for me. It works if there is additional path after the context root. For example, if I add @PostMapping({ "/abc", "/abc/" }) to my request handler method, it works. But not if there is nothing after the "/context/root" – Rafi Aug 14 '23 at 01:06
  • Thank you for you reply. I ended up breaking the context root. So in application.yml, I have server.servlet.context-path set to "/context", and on the request handler method I'm using @PostMapping(path = {"/path", "/path/"}) – Rafi Aug 14 '23 at 01:41
  • ok, breaking the context root worked locally, but not in the pipeline. So I am still looking for an answer. I tried your recommended solution, didn't work for me. – Rafi Aug 15 '23 at 02:02
  • I have to admit, I'm not that deep into Server Configs to fully understand the behavior of your problem. In my applications the provided options worked, but on your side it seems, that there is Tomcat somehow screwing up your efforts. Maybe you wanna take a look at this: https://github.com/spring-projects/spring-boot/issues/22908 Not sure, whether you can take something out of this discussion :/. For any further investigation which not accidentally get a lucky tinkered solution w/o knowing why - thus could break at any time - I hope someone with more knowledge will provide a decent answer. – Slevin Aug 15 '23 at 11:20