Using Jersey and Scala's features for HTTP preconditions

Jersey has a way to plug in processing of Java methods that are HTTP methods (annotated with a @HttpMethod). A resource method dispatch provider analyzes a method signature (of a resource class) and if the signature is suitable returns a request dispatcher that knows how to transform a HTTP request to instances of the method parameter types and transform an instance of the returned type to a HTTP response.

(Note that this plug-in mechanism is not currently exposed as a documented SPI and it is mostly internal to Jersey. However, the plan is to expose this as documented functionality. Also what is presented below is only possible in the latest builds of Jersey so this is all still very much experimental.)

This can make for interesting ways to define and process HTTP methods especially using features of other JVM-compatible languages, like Scala, that support closures.

A RESTful framework like Jersey has some areas where it would be useful for a developer to return some meta-data and a function where by the runtime analyzes the meta-data and from that data the runtime decides if the function should be called or not. Such areas are:

  • precondition support, an HTTP method returns meta-data consisting of an entity tag and/or a last modified date, and a function to be called if preconditions are met. The runtime checks the meta-data against the associated HTTP request headers and if preconditions are not met a 304 (Not Modified) or a 412 (Precondition Failed) response is returned (and the function is not called).
  • variant selection, an HTTP method that may produce one or more representations consisting of one or more media types, languages, character sets and encodings can return a 'sparse' variant matrix and a function to be called with the variant selected by the runtime according to what the client accepts.

I strongly suspect that 100 (Continue) responses can also fall into area and perhaps more generally Comet-based support, for example, using Scala's actors.

I hacked together a proof of concept resource method dispatch provider written in Scala to support preconditions. So i can write a simple Scala resource class as follows:

import PartialResponse._

@UriTemplate("/")
class MethodService {

    @ProduceMime(Array("text/plain"))
    @HttpMethod
    def get() = partialResponse("1234") {
        "RESPONSE"
    }
} 

(Obviously this example is contrived since the entity tag is hard coded).  

The partialResponse is a method imported on the object PartialResponse and it takes as the first parameter a string that is the entity tag "1234" and as the second parameter a function (essentially a closure) that is the block of code that returns the string "RESPONSE".

I deployed this class using the Light Weight HTTP server:

HttpServerFactory.create("http://localhost:9998/method").start()

(Thanks to a recent feature change Jersey now automatically picks up resource classes by scanning the Java class files, by default, using the java class path.) 

And performed a GET request using curl that returns a 304 (Not Modified) response:

> GET /method/ HTTP/1.1
> Host: localhost:9998
> Accept: \*/\*
> If-None-Match: "1234"
>
< HTTP/1.1 304 Not Modified
< Content-length: 0
< Etag: "1234"

So as you can see in this case preconditions are not being met: essentially the client already has the latest state of the resource "/method". 

The code for PartialResponse is as follows:

object PartialResponse {
    def partialResponse(t: String)(body: => Object):
            PartialResponse = {
        val response = new PartialResponse {
            def entityTag() : String = t
            def respond() : Object = body
        }
        response
    }
}

trait PartialResponse {
    def entityTag() : String
    def respond() : Object
}

The partialResponse method creates a anonymous instance of the trait PartialResponse and sets the methods entityTag and respond accordingly. 

The code for the resource method dispatch provider and request dispatcher is presented at the end of this blog and below i highlight the relevant code that performs the precondition calculation:

01  val pr = method.invoke(resource, null).
02          asInstanceOf[PartialResponse]
03
04  val t = new EntityTag(pr.entityTag)
05  var r = request.evaluate(t)
06  if (r == null) {
07      val ar = pr.respond()
08
09      val mediaType = getAcceptableMediaType(request);
10      r = Response.Builder.representation(ar, mediaType).
11              tag(t).build();
12  }
13  response.setResponse(r);

Line 1 and 2 invokes the Java method that is the HTTP method (here it is assumed the method takes no parameters) and the response is cast to a PartialResponse (the resource method dispatch provider ensures that the request dispatcher will only be created for HTTP method with the appropriate return type).

Line 4 obtains the entity tag declared by the HTTP method.

Line 5 evaluates the entity tag for the HTTP request. If the result of the evaluation is not null then preconditions have not been met and the response is returned. If null then preconditions have been met and the respond method of the application is called at line 7.

Lines 9 and 10 build up the response from the value returned by the respond method, the media type and the entity tag.

Of course i am sure this can be vastly improved but the proof of concept shows that it is possible to integrate Scala language features with Jersey in such a manner that has the potential to make it easier and more 'natural' to develop RESTful Web services. Such things are possible with Java by anonymously extending abstract classes, but i think this is unwieldy for developers over the more imperative approach in application code and maybe more so if preconditions are combined with variant selection and potentially 100 (Continue). Of course Java closures in SE 7 would be most welcome especially if there is good compatibility with Scala (perhaps it might be possible to support SE 7 application code with closures and a Jersey runtime compiled to SE 5 or 6 using the same tricks as i have implemented for Scala?).

The next steps are to refine this to work for variant selection and preconditions. I suspect the variants are best handled by Scala's pattern matching, and it will be interesting to see how variant selection and precondition checking can be combined (since the entity tag or last modified to use may depend on the variant that is selected).


import javax.ws.rs.core._
import com.sun.ws.rest.impl.model.method.dispatch.ResourceMethodDispatchProvider
import com.sun.ws.rest.impl.model.method.dispatch.ResourceJavaMethodDispatcher
import com.sun.ws.rest.api.core.HttpRequestContext;
import com.sun.ws.rest.api.core.HttpResponseContext;
import com.sun.ws.rest.api.model.AbstractResourceMethod
import com.sun.ws.rest.spi.dispatch.RequestDispatcher

class ScalaMethodProvider extends ResourceMethodDispatchProvider {

    class PartialResponseDispatcher(arm: AbstractResourceMethod)
            extends ResourceJavaMethodDispatcher(arm) {

        def _dispatch(resource: Any, request: HttpRequestContext,
                response: HttpResponseContext) : Unit = {
            val pr = method.invoke(resource, null).
                asInstanceOf[PartialResponse]

            val t = new EntityTag(pr.entityTag)
            var r = request.evaluate(t)
            if (r == null) {
                val ar = pr.respond()

                val mediaType = getAcceptableMediaType(request);
                r = Response.Builder.representation(ar, mediaType).
                    tag(t).build();
            }
            response.setResponse(r);
        }
    }

    def create(arm: AbstractResourceMethod) : RequestDispatcher = {
        val rt = arm.getMethod().getReturnType()
       
        if (classOf[PartialResponse].isAssignableFrom(rt)) {
            return new PartialResponseDispatcher(arm);
        } else {
            null
        }
    }
}

Comments:

Post a Comment:
  • HTML Syntax: NOT allowed
About

sandoz

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today