13

I have two separate handfuls of REST services in one application. Let's say a main "people" service and a secondary "management" service. What I want is to expose them in separate paths on the server. I am using JAX-RS, RESTEasy and Spring.

Example:

@Path("/people")
public interface PeopleService {
  // Stuff
}

@Path("/management")
public interface ManagementService {
  // Stuff
}

In web.xml I currently have the following set-up:

<listener>
    <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
</listener>

<listener>
    <listener-class>org.jboss.resteasy.plugins.spring.SpringContextLoaderListener</listener-class>
</listener>

<context-param>
    <param-name>resteasy.servlet.mapping.prefix</param-name>
    <param-value>/public</param-value>
</context-param>

<servlet>
    <servlet-name>Resteasy</servlet-name>
    <servlet-class>
        org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher
    </servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>Resteasy</servlet-name>
    <url-pattern>/public/*</url-pattern>
</servlet-mapping>

The PeopleService and ManagementService implementations are just Spring beans. Above web.xml configuration will expose them both on /public (so having /public/people and /public/management respectively).

What I want to accomplish is to expose the PeopleService on /public, so that the full path would become /public/people and expose the ManagementService on /internal, so that its full path would become /internal/management.

Unfortunately, I cannot change the value of the @Path annotation.

How should I do that?

  • Why you can't modify the path annotation? you don't have access to the code? as far as I can see they are interfaces, do you have access to the implementations? – Nicolas Filotto May 14 '16 at 14:23

2 Answers2

16

actually you can. After few hours of debugging I came up with this:

1) Declare multiple resteasy servlets in your web.xml (two in my case)

<servlet>
    <servlet-name>resteasy-servlet</servlet-name>
    <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
    <init-param>
        <param-name>resteasy.servlet.mapping.prefix</param-name>
        <param-value>/openrest</param-value>
    </init-param>       
    <init-param>
        <param-name>resteasy.resources</param-name>
        <param-value>com.mycompany.rest.PublicService</param-value>
    </init-param>
</servlet>

    <servlet>
    <servlet-name>private-resteasy-servlet</servlet-name>
    <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
    <init-param>
        <param-name>resteasy.servlet.mapping.prefix</param-name>
        <param-value>/protectedrest</param-value>
    </init-param>       
    <init-param>
        <param-name>resteasy.resources</param-name>
        <param-value>com.mycompany.rest.PrivateService</param-value>
    </init-param>
</servlet>
<servlet-mapping>
    <servlet-name>private-resteasy-servlet</servlet-name>
    <url-pattern>/protectedrest/*</url-pattern>
</servlet-mapping>      

<servlet-mapping>
    <servlet-name>resteasy-servlet</servlet-name>
    <url-pattern>/openrest/*</url-pattern>
</servlet-mapping>  

Please pay attention to the fact that we initialize personal resteasy.servlet.mapping.prefix and resteasy.resources for each our servlet. Please don't forget to NOT include any botstrap classes as filters or servlets! And disable autoscan as well.

2) Create a filter that cleans up application from the RESTeasy's global information that it saves in context:

public class ResteasyCleanupFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // TODO Auto-generated method stub

    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
            ServletException {
        request.getServletContext().setAttribute(ResteasyProviderFactory.class.getName(), null);
        request.getServletContext().setAttribute(Dispatcher.class.getName(), null);
        chain.doFilter(request, response);


    }

    @Override
    public void destroy() {
        // TODO Auto-generated method stub

    }

}

Register it for any request to your services (here I used it for all requests for simplisity):

<filter>
    <filter-name>CleanupFilter</filter-name>
    <filter-class>com.mycompany.ResteasyCleanupFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>CleanupFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping> 

Thats it!Now you have two different REST services which lays under different prefixes : /openrest which meant to service all public requests and /protectedrest that takes care about all the private stuff in the app.

So why does it work (or why it does not work otherwise)?

When you call openrest instance for the first time it tries to initalize itself and when done saves the state in the global servletContext like this :

 servletContext.setAttribute(ResteasyProviderFactory.class.getName(), deployment.getProviderFactory());
 servletContext.setAttribute(Dispatcher.class.getName(), deployment.getDispatcher());

And if you will let it be your call to your second /protectedrest will get the SAME configuration! That is why you need to clean up this information some where. That is why we used our CleanupFilter which empty the context so brand new rest servlet could initialize itself with all the init parameters we declared.

This is a hack, but it does the trick.

This solution was tested for RESTEasy 2.3.6

EDITED

Works with 3.0.9.final as well!

Anton
  • 1,001
  • 9
  • 23
  • Anton, thanks for taking the time to figure this out! It really helped me! @pbean, I opened a [bug request](https://issues.jboss.org/browse/RESTEASY-1312) for this, on the JBoss issue tracker. – jpaugh May 13 '16 at 17:27
  • 1
    (Plus one) for at least testing. More than I can say for most people that answer questions :-) – Paul Samsotha May 14 '16 at 15:59
  • Really good explanation. My contribution for your post is: the method `request.getServletContext()` only works with a container that supports Servlet 3.0. – Dherik Aug 16 '16 at 21:28
  • 1
    For older Servlet API you can get ServletContext in a Filter as described in [this answer](http://stackoverflow.com/a/10623024/292628) – mmm Aug 17 '16 at 00:16
  • Someone knows how use this configuration with Swagger? I'm trying to generate a different swagger.json for each endpoint, but i'm having problems like this: https://github.com/swagger-api/swagger-core/issues/2099 – Dherik Mar 28 '17 at 17:27
2

AFAIK, you cannot have multiple servlet mappins for your JAX-RS implementation. What you could do is: map RESTEasy to '/' (or '/api' for example if your application has other resources to serve and you don't want the JAX-RS part to interfere), then have the following @Path annotations:

@Path("/public/people")
public interface PeopleService {
  // Stuff
}

@Path("/internal/management")
public interface ManagementService {
  // Stuff
}
Xavier Coulon
  • 1,580
  • 10
  • 15