RESTEasy

Enterprise Mashups with RESTful Web Services and jQuery (Part 2)

RESTful web services and jQuery make it easy to create an enterprise mashup. This 2 part article discusses how to create a simple enterprise mashup using jQuery. Part 1 introduced the basic requirements of your enterprise services, the simplicity of JSON, and how to consume your RESTful web services using jQuery. Part 2 covers consuming services from across the enterprise with JSONP, accessing secure resources, and handling error conditions.

Gathering data from across the enterprise

A mashup generally retrieves data from services throughout the enterprise. You may recall form part 1 that our RESTful web service was located at: http://solutionsfit.com/services/rest/consultants. So where is our mashup located? If it is located in a different domain than solutionsfit.com, say: mycorpdomain.net, the browser will enforce the same-origin policy browser restriction and disallow access.

This restriction can be bypassed through use of JSONP (JSON with padding). For more information on what JSONP is and how to support JSONP within your web services, see my previous posting: Serving up JSONP from your JAX-RS Web Services.

By adding the callback=? parameter to our getJSON invocation in Part 1 we inform jQuery to use JSONP. The parameter is simply added to the URL we are requesting:

jQuery.getJSON(
  'http://solutionsfit.com/services/rest/consultants&callback=?',
  function(data) {
    // ... ...
  }

When jQuery recognizes that a JSONP request is being performed, it takes the function we defined, assigns a unique name, and adds it as a global function. It then replaces the question mark in the callback=? parameter with the name it assigned. This allows the service to wrap the JSON result with the callback function to invoke.

If the service to return our consultants supported JSONP, and jQuery requests: http://solutionsfit.com/services/rest/consultants?callback=jquery12345, the following result would be returned.

jquery12345([{
 "consultant" : {
   "firstName": "Jacob",
   "lastName": "Orshalick",
   "blogFeed": "http://solutionsfit.com/blog/feed"
 },
{
 "consultant": {
   "firstName": "Nirav",
   "lastName": "Assar",
   "blogFeed": "http://assarconsulting.blogspot.com/feeds/posts/default"
 }
}]);

As you can see, the JSON result is wrapped with the callback function jquery12345. Because jQuery adds this function as a global function, it will be called when the service result is evaluated. As a final step, jQuery removes that function once the callback completes.

Accessing secure enterprise web services

So far we have assumed that our services provide wide open access, but most internal enterprise services require authentication. In addition, it is often necessary to restrict what data is returned to a user based on roles and permissions. While this may seem complex, intranet access can provide a unique advantage when using jQuery to create an enterprise mashup.

Intranet environments often provide single-sign on mechanisms that are not available through external service invocations. Given the HTTP-centric approach of REST, the most natural fit for RESTful web service authentication is HTTP authentication. While the specification only provides for BASIC and DIGEST authentication, almost all current browsers support the much more secure HTTP Negotiate mechanism.

The HTTP Negotiate mechanism is the most common use of SPNEGO which allows a client and server to negotiate an authentication mechanism. Many enterprise intranet domains, especially those using Active Directory for authentication, utilize the HTTP Negotiate mechanism (e.g. NTLM or Kerberos) to achieve single-sign on behavior. By securing RESTful web services through the HTTP Negotiate mechanism, and using jQuery to invoke the service, we can rely on the browser’s built in HTTP Negotiate capabilities to authenticate the user.

Let’s say that our previous RESTful web service now required authentication through the HTTP Negotiate mechanism. Now when the URL is requested http://solutionsfit.com/services/rest/consultants, an HTTP 401 Unauthorized response is sent with the following header entry.

WWW-Authenticate: Negotiate

If the browser is accessing a trusted domain it will attempt to authenticate silently through the Negotiate mechanism (either NTLM or Kerberos). As you would expect, this is the same behavior we would see from accessing a general web application protected by the HTTP Negotiate mechanism.

When the jQuery getJSON request is performed, the AJAX invocation receives the same response. As with the previous case, the browser will silently negotiate user authentication and receive the expected JSON result. No additional code is necessary as long as we are accessing a trusted domain and a service supporting the HTTP Negotiate mechanism. The service will silently authenticate the user and provide the appropriate JSON result according to the user’s privileges.

As a JBoss user, I recommend JBoss Negotiation for securing RESTful services through the HTTP Negotiate mechanism. JBoss Negotiation provides a Tomcat authenticator and JAAS login module to add SPNEGO support to JBoss.

Handling failure conditions

So we’ve discussed what happens if things go right, but what if things go wrong? What if the service is down or we can’t authenticate? We need to be able to inform the user that a failure occurred. The current implementation of the jQuery getJSON function does not handle error conditions when using a JSONP request. The call fails silently and any defined error function is ignored.

A simple approach to handle error conditions in a generic way is a timeout. The following implementation demonstrates how a timeout could be applied to our getJSON call in Part 1.

var requestCompleted = false;

window.setTimeout(function() {
  if(!requestedCompleted) {
    jQuery("#consultants")
      .append('<tr><td style="color: red">' +
        'An error occurred while processing this request' +
        '</td></tr>');
  }
}, 5000);

jQuery.getJSON(
  'http://solutionsfit.com/services/rest/consultants&callback=?',
  function(data) {
    requestCompleted = true;

    jQuery.each(data, function(i,item) {
      var consultant = item.consultant;
      var consultantHtml = '<tr>' +
        '<td>' + consultant.firstName + ' '
          + consultant.lastName + '</td>' +
        '<td><a href="' + consultant.blogFeed + '">' +
          'Blog Feed</a></td>' +
        '</tr>';

      jQuery("#consultants").append(consultantHtml);
    });
  }
);

The timeout above is set to 5 seconds, but should be set according to an expected response time for your service. The window.setTimeout function will invoke our defined error handling function after a 5 second period. Unless the request completes within that period and invokes our callback function, the following message will be displayed to the user.

An error occurred while processing this request

While this may be a reasonable approach, there are certainly drawbacks. First, we don’t know what error occurred, simply that the request timed out. If the request fails due to an authentication issue for example, we would likely want to inform the user so they could get the issue resolved. Second, if we set our timeout period too low, we could error out on requests that actually complete. Fortunately, if these issues are of concern, there is an alternative.

The jQuery JSONP project in GoogleCode provides support for error handling. While the usage is not as elegant as the standard getJSON function, it does provide the necessary features to handle these concerns. This StackOvervflow entry provides an example of usage.

Conclusion

As you have seen in this 2 part series, jQuery simplifies enterprise mashup development with RESTful web services. jQuery provides a clean approach to retrieving and rendering service data, bypasses the same-origin policy browser restriction through JSONP support, and allows you to take advantage of HTTP authentication. Hopefully better error handling will be incorporated into JSONP support in future jQuery revisions, but as you have seen, there are ways to get around this issue.

Enterprise Mashups with RESTful Web Services and jQuery (Part 1)

RESTful web services and jQuery make it easy to create an enterprise mashup. This 2 part article discusses how to create a simple enterprise mashup using jQuery. Part 1 introduces what is required of your enterprise services, the simplicity of JSON, and how to consume your RESTful web services using jQuery. Part 2 will cover consuming services from across the enterprise with JSONP, accessing secure resources, and handling error conditions.

Enterprise Web Services: Got REST?

First things first, we need RESTful web services to provide the data we intend to consume. Are there already web services exposed in your enterprise that provide a RESTful API? Do these services support JSON? If not, there are a wide array of technologies that make it simple to expose RESTful web services from your existing applications.

If you have RESTful web services, but they only support an XML result it is quite simple to support JSON with JAX-RS. JAX-RS uses the HTTP Accept header to determine what media type should be sent back as a result. The following HTTP header entry would indicate that the client is requesting a JSON result:

Accept: application/json

RESTEasy provides a portable JAX-RS implementation that makes it simple to expose services supporting a variety of media types. If you happen to be using Seam, exposing RESTful services through RESTEasy is a no-brainer. See the Seam documentation for more details.

Why use JSON when you have XML?

It is now common to expose REST services that return a result in JSON format. The format is described as a lightweight data-interchange format. The major advantage to JSON is that it is JavaScript native format. This means if a RESTful service is invoked through a JavaScript AJAX call and returns a JSON result, the data returned by the service can be used without additional parsing.

For example, we could have the following URL tied to a consultants list resource.

http://solutionsfit.com/services/rest/consultants

When a GET request is received for this URL the following JSON response is generated.

[{
 "consultant" : {
   "firstName": "Jacob",
   "lastName": "Orshalick",
   "blogFeed": "http://solutionsfit.com/blog/feed"
 },
{
 "consultant" : {
   "firstName": "Nirav",
   "lastName": "Assar",
   "blogFeed": "http://assarconsulting.blogspot.com/feeds/posts/default"
 }
}]

If this JSON result was stored in a JavaScript variable named solutionsfitConsultants, I could alert the user of Jacob Orshalick’s blog URL with the following JavaScript snippet.

var consultant = solutionsfitConsultants[0].consultant;

alert(consultant.firstName + ' ' + consultant.lastName);

Obviously this makes it very easy to render results to the user by removing the additional step of parsing that is required with XML.

Request and display JSON data with jQuery

jQuery makes it simple to consume a RESTful service providing a JSON result through a simple AJAX call. Let’s look at an example. The following HTML provides the shell for the service results I want to display in my mashup.

<table width="100%">
  <thead>
    <tr>
      <th>
        Consultant
      </th>
      <th>
        Blog Feed
      </th>
    </tr>
    <tbody id="consultants">
    </tbody>
</table>

The jQuery getJSON function makes it simple to consume the service that provides us the list of consultants. The getJSON function sends an AJAX GET request to the resource URL with an HTTP Accept header of application/json.

jQuery.getJSON(
  'http://solutionsfit.com/services/rest/consultants&callback=?',
  function(data) {
    jQuery.each(data, function(i,item) {
      var consultant = item.consultant;
      var consultantHtml = '<tr>' +
        '<td>' + consultant.firstName + ' ' 
          + consultant.lastName + '</td>' +
        '<td><a href="' + consultant.blogFeed + '">' +
          'Blog Feed</a></td>' +
        '</tr>';

      jQuery("#consultants").append(consultantHtml);
    });
  }
);

Notice that we also use the jQuery each function to loop through the JSON results. Each result in the returned JSON array is set into the variable item.

We then generate the HTML to add to the table for each consultant and set it into the consultantHtml variable. We then select the

element that will contain the consultant results using it’s element ID and use the jQuery append function to append the consultantHtml.

When the page is fully rendered, the resulting HTML will be:

<table width="100%">
  <thead>
    <tr>
      <th>
        Consultant
      </th>
      <th>
        Blog Feed
      </th>
    </tr>
    <tbody id="consultants">
      <tr>
        <td>
          Jacob Orshalick
        </td>
        <td>
          <a target="_blank" 
              href="http://solutionsfit.com/blog/feed/">
            Blog Link
          </a>
        </td>
      </tr>
      <tr>
        <td>
          Nirav Assar
        </td>
        <td>
          <a target="_blank" href=
            "http://assarconsulting.blogspot.com/feeds/posts/default/">
            Blog Feed
          </a>
        </td>
      </tr>
    </tbody>
</table>

Note that only JavaScript libraries and HTML have been used to consume the service. This provides the flexibility to render this content in a web application, a portal, or even a static HTML page!

That’s it for round one of enterprise mashups. Stay tuned for part 2 which will discuss how to gather data from across the enterprise with JSONP and how accessing secured services is made easy.

Serving up JSONP from your JAX-RS Web Services

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.