3

I have Spring Boot application with REST API mapped on /api. I need to define additional servlet on /. I want all request that match /api was handled by REST API and all others requests by the servlet. How to do this?

@SpringBootApplication
public class App {

    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }

    @RestController
    @RequestMapping("/api")
    public class ApiController {

        @GetMapping
        public String get() {
            return "api";
        }
    }

    @Bean
    public ServletRegistrationBean customServletBean() {
        return new ServletRegistrationBean<>(new HttpServlet() {
            @Override
            protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
                resp.getWriter().println("custom");
            }
        }, "/*");
    }   
}

In code above I want something like this:

curl  http://localhost:8080/api/                                                                                                                           
> api⏎       
curl  http://localhost:8080/custom/
> custom

I have tried with filter to redirect requests, but all requests go to custom servlet:

@Bean
public FilterRegistrationBean apiResolverFilter() {
    final FilterRegistrationBean registrationBean = new FilterRegistrationBean<>();
    registrationBean.setFilter((req, response, chain) -> {
        HttpServletRequest request = (HttpServletRequest) req;
        String path = request.getRequestURI().substring(request.getContextPath().length());
        if (path.startsWith("/api/")) {
            request.getRequestDispatcher(path).forward(request, response);
        } else {
            chain.doFilter(request, response);
        }
    });
    registrationBean.addUrlPatterns("/*");
    return registrationBean;
}

This project is available on github: https://github.com/mariuszs/nestedweb

MariuszS
  • 30,646
  • 12
  • 114
  • 155
  • 1
    Have a look at this thread – Gro Mar 20 '19 at 07:06
  • @Gro I have tried and this does not work (or I don't know how to use this) – MariuszS Mar 20 '19 at 08:38
  • 1
    That won't work. The `DispatcherServlet` is mapped to `/` registering another one will destroy the `DispatcherServlet`. Why does it need to be a servlet, why not just a regular controller? What is it you are trying to achieve? – M. Deinum Mar 21 '19 at 19:10
  • @M.Deinum I need to use existing servlet from an external library (GitHttpServlet) – MariuszS Mar 21 '19 at 19:11
  • And why does it need to be mapped to `/`? You can also wrap that servlet in a `` – M. Deinum Mar 21 '19 at 19:14
  • @M.Deinum This was my business requirements. I just want to know if this is possible. – MariuszS Mar 21 '19 at 19:15
  • 1
    Not without destroying the `DispatcherServlet`. You can try a [`ServletWrappingController`](https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/mvc/ServletWrappingController.html) so it sits behind the `DispatcherServlet`. – M. Deinum Mar 21 '19 at 19:17

1 Answers1

3

When mapping a servlet to the root path you will override the mapping for the DispatcherServlet which, by default, is mapped to /.

There are basically 3 solutions you could try

  1. Map the DispatcherServlet to /api and modify the mappings in your controllers
  2. Use a ServletForwardingController to forward the request to the configured but unmapped Servlet
  3. Use a ServletWrappingController to wrap a Servlet instance

Number 2 and 3 are almost the same, with this difference that with option 3 Spring also manages the Servlet instance whereas with option 2, the Servlet container manages the Servlet.

Mapping DispatcherServlet to /api

Option 1 can be an option if all of your controllers are mapped under /api, if they aren't this isn't an option. In your application.properties you would set the spring.mvc.servlet.path to /api. Then you would configure your other Servlet like you did in your question.

Use a ServletForwardingController

Spring provides a ServletForwardingController which will lookup a Servlet in the ServletContext given the name of the servlet and forward the request to it. You will still have to register the Servlet but prevent it from being mapped.

Next you would need a SimpleUrlHandlerMapping to map the URLs to this controller or set it as the default handler (basically a catch all).

@Bean
public ServletForwardingController forwarder() {
   ServletForwardingController controller = new ServletForwardingController();
   controller.setServletName("my-servlet");
   return controller;
}

@Bean
public CustomServlet customServlet() {
    return new CustomServlet();
}

@Bean
public ServletRegistrationBean customServletRegistration() {
    ServletRegistrationBean registration = new ServletRegistrationBean(customServlet(), false);
    registration.setServletName("customServlet");
    return registration;
}

@Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
    SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
    mapping.setDefaultHandler(forwarder());
    mapping.setOrder(LOWEST_PRECEDENCE  - 2);
    return mapping;
}

Use a ServletWrappingController

Spring provides a ServletWrappingController which will internally create and configure a Servlet instance. It acts as an adapter from/to the Servlet to a Spring Controller. You don't have to register the CustomServlet in this case and is thus slightly easier to configure the then ServletForwardingController.

Next you would need a SimpleUrlHandlerMapping to map the URLs to this controller or set it as the default handler (basically a catch all).

@Bean
public ServletWrappingController wrapper() {
   ServletWrappingController  controller = new ServletWrappingController ();
   controller.setServletName("my-servlet");
   controller.setServletClass(CustomerServlet.class);
   return controller;
}

@Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
    SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
    mapping.setDefaultHandler(wrapper());
    mapping.setOrder(LOWEST_PRECEDENCE  - 2);
    return mapping;
}

Depending on your architecture and url structure you might want to go for option 1 or option 3.

M. Deinum
  • 115,695
  • 22
  • 220
  • 224