Monday, January 21, 2013

Ready for IPv6 addresses to visit your web app?

In a java servlet you lookup the client’s IP address by calling request.getRemoteAddr(), which returns a String. But are you expecting a dotted decimal IP address in that String? Your code should be prepared to see IPv6 addresses too.
I recommend looking at Google’s Guava library, a useful collection of commonly useful Java Stuff, i.e. “collections, caching, primitives support, concurrency libraries, common annotations, string processing, I/O, and so forth”. Specific to reading Inet addresses is the class InetAddresses.

With InetAddresses.forString(ipAddr) you can pass in a raw internet address string (whether IPv4 or IPv6) and get back an InetAddress, which you can test to see which type you were returned, either Inet4Address or Inet6Address.

Here’s an example where I am only calling a library if I have an IPv4 address. (Until the lib will lookup IPv6 too.)

  1: import java.net.Inet4Address;
  2: import java.net.InetAddress;
  3: 
  4: import com.google.common.net.InetAddresses;
  5: 
  6: 
  7: String ipAddr = request.getRemoteAddr();
  8: Location loc = null;
  9: try {
 10:     InetAddress ia = InetAddresses.forString(ipAddr);
 11:     // TODO fix geo library to handle IPv6 addresses
 12:     if ( ia instanceof Inet4Address ) {
 13:         loc = Geo.DB.lookup(ipAddr);
 14:     }
 15: }
 16: catch ( IllegalArgumentException e ) {
 17:     // the string was not a valid IPv4 or IPv6 address
 18:     loc = null;
 19: }

Another thing I am interested in from the Guava library is a means to produce a canonical form of an IPv6 address. There is a recommendation proposed (IETF RFC5952) for a canonical form of IPv6, and it calls out some good reasons why we should care. The actual proposal itself is very simple, having only five concerns and are what you’d expect.
I have not thoroughly examined yet what Guava does to format IPv6 addresses. That’s next.


Update 1/22/2013:  The function InetAddresses.toAddrString(InetAddress ip) returns the string representation of the IP address, and for IPv6 addresses, it adheres to RFC 5952. Hat tip to +Paul Marks for pointing me to the right version of the API.

Wednesday, January 2, 2013

Jersey @Provider example

I’ve found many examples of writing a Jersey @Provider to implement data marshalling, or mapping exceptions into a Response, but I've found very little on how to inject your own data types into your Jersey resource methods.

My code used to look something like this:

@POST
@Path("/create")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response createThing(@Context HttpServletRequest request, String arg) {
    UserSession us = /* code to extract UserSession from cookie in request */
    /* more code here */
}

But I wanted it to look like this:

@POST
@Path("/create")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response createThing(@Context UserSession us, String arg) {
    /* more code here */
}

Doing it this way cleans up the code more than the above snippets suggest. I eliminate some exception handling from all the resource functions that need the UserSession (which are many), plus I get a good separation of concerns. It’s just dependency injection.

So the @Provider class to support the new code looks like this:

import java.lang.reflect.Type;
import javax.ws.rs.core.Context;
import javax.ws.rs.ext.Provider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sun.jersey.api.core.HttpContext;
import com.sun.jersey.core.spi.component.ComponentContext;
import com.sun.jersey.core.spi.component.ComponentScope;
import com.sun.jersey.server.impl.inject.AbstractHttpContextInjectable;
import com.sun.jersey.spi.inject.Injectable;
import com.sun.jersey.spi.inject.InjectableProvider;
@Provider
public class UserSessionProvider extends AbstractHttpContextInjectable<UserSession> implements InjectableProvider<Context, Type> {
    final static private Logger logger = LoggerFactory.getLogger(UserSessionProvider.class);
    @Override
    public Injectable<UserSession> getInjectable(ComponentContext ic, Context a, Type c) {
        if (c.equals(UserSession.class)) {  
            return this;  
        }  
        return null;
    }
    @Override
    public ComponentScope getScope() {
        return ComponentScope.PerRequest;
    }
    @Override
    public UserSession getValue(HttpContext context) {
        try {
            // CookieUserSession takes care of the marshalling/validation of data in the cookie
            CookieUserSession cookie = CookieUserSession.checkForCookie(context.getRequest().getCookies());
            return cookie.recoverAllFields(); // returns a UserSession, clearly
        }
        catch (CookieTamperedException e) {
            logger.error(e.getMessage(), e);
            throw new RsException(e); // my subclass of WebApplicationException
        }
    }
}

It took me a while to sort out which class I needed to extend (and interface to implement). A number of trials and errors gave me Jersey exceptions complaining that it could not find an injector for my resource function. Messages like this:

INFO: Initiating Jersey application, version 'Jersey: 1.14 09/09/2012 07:21 PM'
Jan 02, 2013 10:20:12 PM com.sun.jersey.spi.inject.Errors processErrorMessages
SEVERE: The following errors and warnings have been detected with resource and/or provider classes:
  SEVERE: Missing dependency for method public javax.ws.rs.core.Response com.example.rs.MyService.createThing(com.example.stateful.UserSession,java.lang.String) at parameter at index 0
  SEVERE: Method, public javax.ws.rs.core.Response com.example.rs.MyService.createThing(com.example.stateful.UserSession,java.lang.String), annotated with POST of resource, class com.example.rs.MyService, is not recognized as valid resource method.

Hat tip to Antoine Vianey’s blog post where one of his examples demonstrated this.