Using ColdFusion and LCDS in AIR-powered Flex Applications | Home | Introducing Kindling, the first product from Arc90

Filed under Development on June 13, 2008 by Avi Flax

Building RESTful Web Apps with Groovy and Restlet, Part 2: Resources

In Part 1 of this series, we set up a development environment for our apps, and created and tested a simple “Hello, world” example. In this installment, we'll make our app more realistic and useful.

If we revisit the current makeup of restfulapp.groovy, we see that there are two elements: the class RequestHandler, and the line which creates and starts an HTTP server. RequestHandler has only one method: handle(), which is called for every request. handle() checks whether the request method is GET, which is the only method supported by the app at this stage, and responds with “Hello, world!” if it is, and with an error response if it isn't.

It works, but there's definitely some key elements missing from the picture at this point – namely resources and representations. While our RequestHandler is indeed responding to requests and is therefore doing actual work, it's not particularly RESTful. There's no clear resource specified, and no representations of the state of that resource transferred back and forth. So that's what we'll add to the app now.

Let's update RequestHandler so it's no longer a generic “request handler” class, but instead models a resource. I'm not going to go into REST theory here to discuss what a resource is; if you could use a primer see your favorite REST tutorial or reference, or refer to the relevant section of The Dissertation.

So what resource should our class model? For the purposes of this article, we could choose almost anything. But we'll want something that'll allow us to demonstrate some important RESTful concepts, and to build something that resembles real-world resources in transactional software systems.

How about a mailbox? It's a good candidate because it has state of its own, which would be retrieved with GET, might support both PUT to update its own state, and POST to accept entities for processing, and maybe even DELETE. It also might have “child resources“: messages.

Let's try changing the name of RequestHandler to MailboxResource and see what it looks like:

class MailboxResource extends Restlet
{
    void handle(Request request, Response response)
    {
    	if (request.method == Method.GET)
		{
			response.setEntity("Hello, world!", MediaType.TEXT_PLAIN)
		}
		else
    	{
    		// The request method is not GET, so set an error response status
			response.setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED)
			response.setAllowedMethods([Method.GET] as Set)
    	}
    }
}

Next, let's change the handling of GET to return a meaningful representation of the current state of the mailbox:

	if (request.method == Method.GET)
	{
		response.setEntity("access: closed; messages: 0;", MediaType.TEXT_PLAIN)
	}

OK, it's primitive. But it's a plausible representation of the current state of the mailbox – and that's all we need at this point.

Next, let's add support for PUT, which a client might use to update the open/closed state of the Mailbox, and POST which a client might use to put a message into the Mailbox.

A first attempt to do so might look like this:

		switch (request.method)
		{
			case Method.GET:
				response.setEntity("access: closed; messages: 0;", MediaType.TEXT_PLAIN)
				break
			
			case Method.PUT:
				if (request.attributes.access)
				{
					this.access = request.attributes.access
				}
				else
				{
					response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "The request representation must include the parameter 'access'.")
				}
				break
			
			case Method.POST:
				if (request.attributes.messageContent)
				{
					createMessage(request.attributes.messageContent)
				}
				else
				{
					response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "The request representation must include the parameter 'messageContent'.")
				}
				break
							
			default:
				// The request method is not allowed; set an error status
				response.setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED)
				response.setAllowedMethods([Method.GET, Method.PUT, Method.POST] as Set)
		}

This code would work, as long as we implemented the field this.access set on line 10, and the method createMessage(), called from line 21. But as you can see, our once-svelte method is getting fairly unwieldy, and at this point we're still only doing very basic processing for our requests. You can imagine that once we need some more complex processing, our method could become impossible to work with.

OK, no problem, we all know what to do in situations like this: create class methods to handle each of our allowed HTTP methods. After which, MailboxResource might look something like this:

class MailboxResource extends Restlet
{
    void handle(Request request, Response response)
    {
		switch (request.method)
		{
			case Method.GET:
				handleGet(request, response)
				break
			
			case Method.PUT:
				handlePut(request, response)
				break
			
			case Method.POST:
				handlePost(request, response)
				break
							
			default:
				// The request method is not allowed; set an error status
				response.setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED)
				response.setAllowedMethods([Method.GET, Method.PUT, Method.POST] as Set)
		}
    }

    void handleGet(request, response)
    {
	    response.setEntity("access: closed; messages: 0;", MediaType.TEXT_PLAIN)	
    }

    void handlePut(request, response)
    {
		if (request.attributes.access)
		{
			this.access = request.attributes.access
		}
		else
		{
			response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "The request representation must include the parameter 'access'.")
		}    	
    }

    void handlePost(request, response)
    {
		if (request.attributes.messageContent)
		{
			createMessage(request.attributes.messageContent)
		}
		else
		{
			response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "The request representation must include the parameter 'messageContent'.")
		}    	
    }
}

Definitely easier to read, but still fairly verbose. That switch statement is looking kinda dumb at this point; it's not doing much.

So let's use some dynamic Groovy magic to get rid of it:

    void handle(Request request, Response response)
    {
    	def allowedMethods = [Method.GET, Method.PUT, Method.POST] as Set
    	
    	if (request.method in allowedMethods)
    	{
			def handleMethodName = "handle" + request.method.name.substring(0, 1).toUpperCase() + request.method.name.substring(1).toLowerCase()
			this."$handleMethodName"(request, response)
		}
		else
		{
			// The request method is not allowed; set an error status
			response.setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED)
			response.setAllowedMethods(allowedMethods)			
		}
	}

OK, now we're getting somewhere! Easy dynamic method invocation – pretty cool.

Thing is, our handle() method is at this point barely doing anything – just figuring out whether the requested HTTP method is allowed, and calling the appropriate class method if it is, or returning an error if it isn't. We shouldn't have to implement this standard logic in every single one of our resources, that'd be pretty redundant.

Thankfully, the developers of Restlet realized this, and they created a very useful class which takes care of much of this sort of overheard for you. It's named, simply, Resource.

Let's convert MailboxResource to extend Resource. We'll do this in a few steps, to illustrate some of the work that Resource does for us.

Let's start MailboxResource from scratch:

#!/usr/bin/env groovy -classpath org.restlet.jar:com.noelios.restlet.jar

import org.restlet.*
import org.restlet.data.*
import org.restlet.resource.*

class MailboxResource extends Resource
{
	static String access = 'closed'
	static int messageCount = 0

	def MailboxResource(Context context, Request request, Response response)
	{
		super(context, request, response)
		variants.add(new Variant(MediaType.APPLICATION_WWW_FORM))
		modifiable = true
	}

}

// Create a Finder which will create new instances of MailboxResource as needed
finder = new Finder(new Context(), MailboxResource.class)

/* Create a new HTTP Server on port 3000, pass it the Finder,
 to which it will pass all incoming Requests, and start it. */
new Server(Protocol.HTTP, 3000, finder).start()

As you can see, there's a new import on line 5, because Resource is in a Restlet sub-package. Now's also a good time to add some static fields to store the state of the resource, on lines 9 and 10. Normally we wouldn't store state this way; this is just more convenient for now than using a real persistent data store.

Next, you'll see that MailboxResource now has a constructor. This may seem like annoying overhead, but trust me, it'll be worth it. For example: the framework will pass the request and response to the constructor, which passes them to the super constructor, which stores them in class fields. This means there's no more need to pass the request and response around to every method – we can just refer to this.request and this.response.

There's some other good things going on in the constructor. On line 15 we add a Variant to the variants collection; this is used for HTTP content negotiation – which Resource does for you. That's right – one of the trickiest aspects of implementing a sophisticated RESTful web app, and you get it for free just by using Resource. Good deal! I'll demonstrate content negotiation in a later article in this series.

The last line of the constructor sets the resource as modifiable; by default instances of Resource only allow GET; setting modifiable to true allows PUT, POST, and DELETE.

We don't want to allow DELETE for now, so let's add this method:

	def boolean allowDelete()
	{
		return false
	}

It's time to add our request handling logic back into the application. Let's add this method:

	def Representation represent(Variant variant)
	{
		def form = new Form()
		
		form.add("access", access)
		form.add("messages", messageCount as String)
		
		return form.webRepresentation
	}

Notice that the new method isn't named “handle” or “handleGet”; instead it's represent(). There are many good and useful reasons for this, but I'm not going to go into all of them at the moment. I'll just point out one for now: after processing a PUT or POST request, a common behaviour is to return a representation of the new, altered, state of the resource. So it's not only when responding to GET that we need to construct a representation of the resource; it's also frequently needed for other methods. Therefore, a specific method for this need makes a lot of sense.

All we need to do now is implement PUT and POST. Here's PUT:

	def void storeRepresentation(Representation representation)
	{
		def form = new Form(representation)

		if (form.getFirst("access"))
		{
			access = form.getFirstValue("access")
			response.entity = represent()
		}
		else
		{
			response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "The request representation must include the parameter 'access'.")
		}
	}

And as you can see, the HTTP method PUT is handled by a method named storeRepresentation(). This is because Restlet attempts to model REST semantics in its API. The framework passes the request representation into the method, because it is the main input of the operation.

On line 3 we see another useful Restlet class: Form. As you might guess, this is convenient for working with web forms.

Finally, let's add support for POST:

	def void acceptRepresentation(Representation representation)
	{
		if (access != 'open')
		{
			response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "A message can only be put into the mailbox if it is open.")
			return
		}

		def form = new Form(representation)

		if (form.getFirst("message_content"))
		{
			messageCount++
			response.entity = represent()
		}
		else
		{
			response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "The request representation must include the parameter 'message_content'.")
		}
	}

Normally, an operation like this would create a sub-resource, and the URL of that resource would be returned as a response header, along with the response status 201 Created. This will be illustrated in a later article in this series.

And that's it, we're done with our first Resource! We just need to make one last change to our script to make it all work; we need to change the last line to:

// Create a Finder which will create new instances of MailboxResource as needed
finder = new Finder(new Context(), MailboxResource.class)

/* Create a new HTTP Server on port 3000, pass it the Finder,
 to which it will pass all incoming Requests, and start it. */
new Server(Protocol.HTTP, 3000, finder).start()

I'll explain the Finder class in a later article.

As the comments indicate, a new instance of MailboxResource will be created for every single HTTP request. At first this might seem inefficient, but it means that every Resource class is inherently threadsafe – no small value there. And it's still plenty fast: my ab script from Part 1 now yields 688 requests per second.

Tests!

Let's update testfulapp.groovy to test our new resource:

#!/usr/bin/env groovy -classpath org.restlet.jar:com.noelios.restlet.jar

import org.restlet.*
import org.restlet.data.*

client = new Client(Protocol.HTTP)
mailboxResourceUrl = "http://localhost:3000/mailbox"


/*** Test 1: at first, access to the mailbox should be closed ***/
response = client.get(mailboxResourceUrl)

assert response.status.code == 200
assert response.entity.mediaType.equals(MediaType.APPLICATION_WWW_FORM, true)
assert response.entityAsForm instanceof Form
assert response.entityAsForm.getFirstValue("access") == "closed"


/*** Test 2: we shouldn't be able to post messages when the mailbox is closed ***/
form = new Form()
form.add("message_content", "whatever")
response = client.post(mailboxResourceUrl, form.webRepresentation)

assert response.status.code == 400
assert response.isEntityAvailable() == false


/*** Test 3: change the mailbox access to "open" ***/
form = new Form()
form.add("access", "open")
response = client.put(mailboxResourceUrl, form.webRepresentation)

assert response.status.code == 200
assert response.isEntityAvailable() == true
assert response.entityAsForm instanceof Form
assert response.entityAsForm.getFirstValue("access") == "open"

messageCount = response.entityAsForm.getFirstValue("messages") as Integer


/*** Test 4: we should be able to post a message now that the mailbox is open ***/
form = new Form()
form.add("message_content", "whatever")
response = client.post(mailboxResourceUrl, form.webRepresentation)

assert response.status.code == 200
assert response.isEntityAvailable() == true
assert response.entityAsForm instanceof Form
assert response.entityAsForm.getFirstValue("access") == "open"
assert response.entityAsForm.getFirstValue("messages") as Integer == messageCount + 1


/*** Test 5: change the mailbox access to "closed" ***/
form = new Form()
form.add("access", "closed")
response = client.put(mailboxResourceUrl, form.webRepresentation)

assert response.status.code == 200
assert response.isEntityAvailable() == true
assert response.entityAsForm instanceof Form
assert response.entityAsForm.getFirstValue("access") == "closed"


println "\nAll tests passed successfully!\n"

That's all, folks!

And that's it! We've now used Restlet to implement a truly RESTful resource. And we used some Groovy goodness as well: switch, Groovy Truth, dynamic method invocation, casting, and more.

I hope this second installment in the series has been interesting and valuable for you, and I look forward to your feedback, comments, critiques, and constructive criticism.

Coming up in Part 3: Routing. And it'll be shorter than this monstrosity, I promise.

Full source of restfulapp.groovy

#!/usr/bin/env groovy -classpath org.restlet.jar:com.noelios.restlet.jar

import org.restlet.*
import org.restlet.data.*
import org.restlet.resource.*

class MailboxResource extends Resource
{
	static def access = 'closed'
	static def messageCount = 0

	def MailboxResource(Context context, Request request, Response response)
	{
		super(context, request, response)
		variants.add(new Variant(MediaType.APPLICATION_WWW_FORM))
		modifiable = true
	}
	
	def boolean allowDelete()
	{
		return false
	}

	def Representation represent(Variant variant)
	{
		def form = new Form()
		
		form.add("access", access)
		form.add("messages", messageCount as String)
		
		return form.webRepresentation
	}
	
	def void storeRepresentation(Representation representation)
	{
		def form = new Form(representation)

		if (form.getFirst("access"))
		{
			access = form.getFirstValue("access")
			response.entity = represent()
		}
		else
		{
			response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "The request representation must include the parameter 'access'.")
		}
	}
	
	def void acceptRepresentation(Representation representation)
	{
		if (access != 'open')
		{
			response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "A message can only be put into the mailbox if it is open.")
			return
		}
	
		def form = new Form(representation)

		if (form.getFirst("message_content"))
		{
			messageCount++
			response.entity = represent()
		}
		else
		{
			response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST, "The request representation must include the parameter 'message_content'.")
		}
	}
}

// Create a Finder which will create new instances of MailboxResource as needed
finder = new Finder(new Context(), MailboxResource.class)

/* Create a new HTTP Server on port 3000, pass it the Finder,
 to which it will pass all incoming Requests, and start it. */
new Server(Protocol.HTTP, 3000, finder).start()

Post a Comment Digg Del.icio.us

Trackback Pings (TrackBack URL for this entry)

http://www.arc90.com/cgi-bin/mt4/mt-tb.cgi/161.

Comments

Great article. Thanks!

Looking forward to part 3 :)

Posted on July 1, 2008 5:20 PM by Joe Mulvaney

Me too, I would really like to see soon more articles on this, you have helped me a lot in understanding the use of groovy with restlet, a very compelling alternative to other options, including plain java.

Cheers!

Posted on August 11, 2008 11:10 AM by Carlos Terra

Post a Comment:

Using ColdFusion and LCDS in AIR-powered Flex Applications | Main | Introducing Kindling, the first product from Arc90