I’ve been working with Restlet to expose a RESTful api interface to the data model for one of my projects. Restlet is a super flexible library allowing one to configure and access all the properties of HTTP through a REST-oriented API.
The application bootstrapping options are also super flexible, allowing one to configure how routes are resolved and nest routes for cascading capabilities. I ran into a small caveat when I tried to configure extension based content negotiation. Basically, the idea of extension based content negotiation, is that instead of using “Accept” headers, one can append a mime extension to their request URI to request a particular return format. Say, we have a http://localhost/api/resource uri, one can request xml or json formats by simply doing http://localhost/api/resource.xml or http://localhost/api/resource.json. Of course your resource has to support these formats. The documentation on this type of content negotiation is non-existent. I had to scour a bunch of users group messages and javadocs before I figured it out. I figured I’ll shared if someone else is interested.
My applications is written in Scala, so examples will be provided as such. I’m sure any experienced developer can easily discern the java equivalent.
First, in your application bootstrapping, you must turn on the extensionsTunnel option. Here is my code, which also demonstrates nested routes. Then, in your resource you must conditionally infer the MediaType provided and emit the representation of this resource based on it.
import org.restlet.{Restlet, Application => RestletApplication}
import scala.xml._
//... other imports excluded
class TestApplication extends RestletApplication {
override def createInboundRoot: Restlet = {
val apiRouter = new Router(getContext)
apiRouter.attach("/test", classOf[TestResource])
val rootRouter = new Router(getContext)
rootRouter.attach("/api/v1", apiRouter).getTemplate.setMatchingMode(Template.MODE_STARTS_WITH)
getTunnelService.setExtensionsTunnel(true)
return rootRouter
}
}
class TestResource extends ServerResource {
@Get("xml|json")
def represent(v:Variant):String = {
return v.getMediaType match {
case MediaType.TEXT_XML | MediaType.APPLICATION_XML => <response><message>Hello from Restlet!</message></response>.toString
case MediaType.APPLICATION_JSON => "{\"message\": \"Hello from Restlet\"}"
}
}
}
First, the root router’s matching mode must be set to Template.MODE_STARTS_WITH, otherwise it will try to match based on full absolute uri path and not find any nested resources. So the matching mode is very important in the case where you’re working with nested resources.
Second, you set the extensions tunnel property to true: getTunnelService.setExtensionsTunnel(true). This will turn on the extension tunneling service and perform content negotiation based on the URI’s extension. Note: if an extension is not provided, it will resort to first available representation supported by the resource. It can get more complicated I believe based on other configurations, but this is what happens in the most simple scenario.
Now, with content negotiation on, the resource has to conditionally infer the proper MediaType requested and provide its representation for the MediaType. In Scala this is very elegantly done using the super flexible match/case construct. This construct can be used as Java’s switch statement, but it is way more powerful and allows for advanced pattern matching. As you can see, I check for both xml and json media types and provide the proper representation. The supported media types are handled through @Get annotation. For more info, see Restlet’s annotations and Resource documentation.
Now, accessing the resources yields the following results:
$ curl http://localhost:8080/api/v1/test.xml$ curl http://localhost:8080/api/v1/test.json {"message": "Hello from Restlet"} Hello from Restlet
Tags: Java, REST, scala, web development, Web Services, xml