If you are developing RESTful services that will be consumed by AJAX clients on different servers, you will likely need to support JSONP. JSONP allows your RESTful web services to support cross-domain communication by enabling your clients to bypass the same-origin policy browser restriction. While some JAX-RS implementations support JSONP, this article demonstrates how any JAX-RS web service can support JSONP through a servlet filter.

Why you would use JSONP

Cross-domain communication is a common problem when developing rich web clients that utilize RESTful web services. Browsers impose the same-origin policy which is described in depth in: Cross-domain communications with JSONP.

To paraphrase the problem, a script loaded from one location, say http://solutionsfit.com/blog/, could not execute an AJAX request that gets properties from a service outside of the domain solutionsfit.com. The diagram below describes this scenario.

If the AJAX request to geonames.org returned a basic JSON response, the browser would not allow access to this data. This is a problem for many AJAX applications, especially mashups, which may access a number of resources to generate content. JSONP (JSON with padding) solves this problem by wrapping the returned data with a function.

The function is invoked as a callback once the AJAX call completes with the JSON results passed as an argument. This requires that the callback function be defined in the web page. So, in the diagram above, if the response from geonames.org returns a function that takes the JSON result as an argument, we can bypass the same-origin policy. This of course requires the web service being invoked to support JSONP.

Creating a Servlet Filter to process JSONP requests

JAX-RS does not support JSONP by default. Some implementations provide an extension to produce JSONP content but some do not (see the RESTEasy JIRA issue). As a Seam user, RESTEasy is the perfect option for exposing RESTful services due to it’s tight integration. As RESTEasy does not currently support JSONP, I needed a solution. Fortunately, you can add support of JSONP using a servlet filter. The following implementation is a naive approach, but shows the general idea.

public class JSONPRequestFilter 
     extends org.jboss.seam.web.AbstractFilter {
  public void doFilter(ServletRequest request, ServletResponse response, 
      FilterChain chain) throws IOException, ServletException {
    if (!(request instanceof HttpServletRequest)) {
       throw new ServletException("This filter can " +
         " only process HttpServletRequest requests");
    }

    HttpServletRequest httpRequest = (HttpServletRequest) request;
      
    if(isJSONPRequest(request))
    {
      ServletOutputStream out = response.getOutputStream();

      out.println(getCallbackParameter(httpRequest) + "(");
      chain.doFilter(request, response);
      out.println(");");

      response.setContentType("text/javascript");
    }
    else
    {
      chain.doFilter(request, response);
    }
  }

  private String getCallbackMethod(HttpServletRequest httpRequest)
  {
    return httpRequest.getParameter("callback");
  }

  private boolean isJSONPRequest(HttpServletRequest httpRequest)
  {
    String callbackMethod = getCallbackMethod(httpRequest);
    return (callbackMethod != null && callbackMethod.length() > 0);
  }
}

This filter processes any request that provides a callback function parameter (the signature of a JSONP request). It wraps the JSON return data with a function, the value of the callback parameter. In my appication I needed to support multiple media return types so I implemented a more robust approach that checks the Accept header to verify that text/javascript or application/javascript is an accepted media type. It also wraps the HttpServletRequest to inform RESTEasy that application/json is the preferred media type. This ensures that RESTEasy provides a JSON response.

Configuring the JSONP Servlet Filter

To configure the filter to apply to RESTful web service requests, you would simply add the following to your project’s web.xml.

<filter>
  <filter-class>com.solutionsfit.rest.JSONPRequestFilter</filter-class>
  <filter-name>JSONPRequestFilter</filter-name>
</filter>
  
<filter-mapping>
  <filter-name>JSONPRequestFilter</filter-name>
  <url-pattern>/seam/resource/rest/*</url-pattern>
</filter-mapping>

The url-pattern should match the base URL pattern for your RESTFul web service requests. The default base URL for a Seam configuration is shown above. Once this is complete, you can bypass the same-origin policy by making JSONP requests to your JAX-RS web services.

Security Considerations

As a final note, be aware that there are security considerations associated with the use of JSONP and cross-site request forgery attacks. The same-origin policy exists to eliminate this issue, so appropriate precautions should be taken to ensure that security is enforced. Always ensure that you understand the implications of using JSONP prior to enabling it for your web services and prior to invoking a service that provides JSONP support.