3

The context

I am currently working on an educational project. This implies two Spring Boot REST servers. One is an actual server, which does some processing.

The one I'm interested in is the other. It is a proxy which will redirect all calls to the first one. So that when I call http://localhost:8080/foo, my proxy server will in turn call http://localhost:8090/foo. And if the first server returns A, the proxy will return {"proxied": A, "someInformationAboutThisCall": B}.

I managed to get to this point with some probably inelegant but functioning code of which I give an excerpt below. The key here is that I use @RequestMapping("**") to achieve this. The next step is to design an interface that will make my additional information immediately legible, which is basically the point of this project. If I remove all @RequestMapping("**"), it works just fine.

The question

Now my problem is the following: having used @RequestMapping("**"), I cannot serve static content (the calls get redirect to the other REST server, which does not serve static content). How could I configure Spring Boot/Spring MVC to ignore resources available as static content when mapping the requests, or make the PathResourceResolver prioritary over my controller?` Or should I serve my static content from yet another JVM/server?

Thanks in advance for your help!

Edit of interest: while doing some tests, I discovered that the static content is served, with some restrictions, if I use @RequestMapping("*").

  • /index.html generates an error page (as does more generally any static content directly in public)
  • /itf/index.html works (as does more generally any file in public/itf or any other subdirectory of public)
  • /itf does not work: Spring Boot seems unaware of an index file in it. I must specify a full URI, down to the specific file I want to display.

This however does not work at all with @RequestMapping("**"), which I need.

The tentatives

I tried using a WebMvcConfigurerAdapter with an HandlerInterceptorAdapter (found on SO, SO again and many other places on the Internet), but could not start my project anymore because Spring boot then does not find the InterceptorRegistry bean (has there been recent changes in Spring Boot? I'm using the version 1.5.3.RELEASE).

I also tried some anti-matching but not only does it not work, it also feels very very dirty (and this whole project is probably not optimal, so that's saying a lot).

The code samples for the curious

My "proxy" controller

Note: you can suggest better ways to realize this in comments. Please keep in mind that, though I'm always open to enhancement suggestions, this was not my question.

@RestController
public class ProxyController {

    @Value("${monitored.url.base}") // "http://localhost:8090"
    private String redirectBase;


    @RequestMapping(value = "**", method = {RequestMethod.POST, RequestMethod.PUT})
    public ProxiedResponse proxifyRequestsWithBody(HttpServletRequest request, @RequestHeader HttpHeaders headers, @RequestBody Object body) throws URISyntaxException {
        return proxifyRequest(request, headers, body);
    }

    @RequestMapping(value = "**")
    public ProxiedResponse proxifyRequestsWithoutBody(HttpServletRequest request, @RequestHeader HttpHeaders headers) throws URISyntaxException {
        return proxifyRequest(request, headers, null);
    }

    private ProxiedResponse proxifyRequest(HttpServletRequest request, @RequestHeader HttpHeaders headers, @RequestBody Object body) throws URISyntaxException {
        final RequestEntity<Object> requestEntity = convertToRequestEntity(request, headers, body);

        // call remote service
        final ResponseEntity<Object> proxied = restTemplate.exchange(requestEntity, Object.class);

        // Return service result + monitoring information
        final ProxiedResponse response = new ProxiedResponse();
        response.setProxied(proxied.getBody());
        // set additional information

        return response;
    }

    // Won't work properly for POST yet
    private <T> RequestEntity<T> convertToRequestEntity(HttpServletRequest request, HttpHeaders headers, T body) throws URISyntaxException {
        // Build proxied URL
        final StringBuilder redirectUrl = new StringBuilder(redirectBase).append(request.getRequestURI());
        final String queryString = request.getQueryString();
        if (queryString != null) {
            redirectUrl.append("?").append(queryString);
        }

        // TODO enhancement: transmit headers and request body to make this a real proxy
        final HttpMethod httpMethod = HttpMethod.valueOf(request.getMethod());
        return new RequestEntity<>(body, headers, httpMethod, new URI(redirectUrl.toString()));
    }
}

My dirty attempt at excluding static resources URLs

@Configuration // adding @EnableWebMvc did not solve the problem
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {

    private static class StaticResourcesHandlerInterceptor extends HandlerInterceptorAdapter {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            final String requestURI = request.getRequestURI();
            if (requestURI == null || "/".equals(requestURI) || "/index.html".equals(requestURI) || requestURI.startsWith("/assets")) {
                return super.preHandle(request, response, null);
            }

            return super.preHandle(request, response, handler);
        }
    }

    @Autowired
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new StaticResourcesHandlerInterceptor()).addPathPatterns("/**");
    }
}
Chop
  • 4,267
  • 5
  • 26
  • 58

2 Answers2

1

You can split the path into a wild-card, and a named path variable which must match a negative lookahead regular expression.

@RequestMapping("/{variable:(?!static).*}/**")

You can then use @PathVariable String variable as an argument of your controller method to obtain the value of variable if you need to pass it.

(Would rather have written a comment but I have insufficient reputation)

muxmuse
  • 385
  • 2
  • 9
  • You should avoid posting answers that would be more appropriate as comments, otherwise you may attract downvotes or flags. – Ctrl S Dec 04 '18 at 14:01
0

Try to add the @EnableWebMvc annotation to your configuration:

@Configuration
@EnableWebMvc
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {

...

}
andrearro88
  • 254
  • 3
  • 9