Saturday, August 29, 2009

Groovy RESTClient, HTTPBuilder, and PUT responses with no body

In working with a RESTful web service that allows PUT requests to upload files, I came across an slight annoyance with the put() convenience method in the Groovy RESTClient. Up until now, I've been absolutely amazed with how easy RESTClient makes my life in working with RESTful web services, and I'll continue to use it in doing REST development. However, in this one case, RESTClient wouldn't work nicely for me.

Here's the scenario: I upload a zip file to the service so that it can use the data in that file to populate some things (see my post Groovy, RESTClient, and PUTting zip files for one way to get RESTClient to handle zip files). I watch the log on the server and see that it's handling that file just fine; however, my Groovy script errors out! What's going on?

Here's what the Groovy code looked like at this point:

def file = new File("data_file.zip")
def rest = new RESTClient( 'http://localhost:8080/server/rest/' )
rest.encoder.'application/zip' = { ... handle zip files ... }
rest.put( path: "data/data_file.zip", body: file, requestContentType: 'application/zip' )


It turns out that the RESTful service, once it gets the file, just sends back an HTTP "201 Created" response code. No body to the response at all because, really, there's none needed. However, by default, the RESTClient put() method wants a response body to parse and return, so it throws a NullPointerException when it tries to parse that "nothing".

Since I didn't see a way of telling RESTClient not to expect a response body, I could have just lived with the exception, catching it and moving along. But the exception was being thrown before the response object was passed back, I couldn't even check the status of my request to see if the request was successful or if there were problems. Fortunately, RESTClient is really just a set of convenience wrappers around the main HTTPBuilder, so I was able to implement my request using HTTPBuilder.

HTTPBuilder has the nice feature of allowing you to assign different response handlers to different HTTP response codes, so I could basically tell it not to bother trying to parse '201' responses and just print a "success" message. Here's how I ended up setting up the PUT request using HTTPBuilder:

import groovyx.net.http.HTTPBuilder
import groovyx.net.http.Method

def file = new File("data_file.zip")

def http = new HTTPBuilder('http://localhost:8080/server/rest/')
http.encoder.'application/zip' = { ... handle zip file ... }

http.request(Method.PUT) { req ->
uri.path = 'data/data_file.zip'
send("application/zip", file)

response.'201' = { resp ->
println resp.status
}

response.success = { resp, object ->
println resp.status
}

response.failure = { resp ->
println resp.statusLine
}
}


Of course, we could do a lot more with the response objects, but at this point, all we want to know is whether our request failed or succeeded, without having to worry about a response body. While it's definitely not as convenient as RESTClient, with the HTTPBuilder implementation in place, the file uploaded just fine, and there were no errors on the Groovy end of things.

4 comments:

Mike Miller said...

Jeffrey,

I encountered the same problem with the RESTClient code. I was using the RESTEasy package and testing against the sample code, which returns just the 201 and updates the location header. I exchanged some emails with the creator or HTTPBuilder/RESTClient and he helped me get around my problem by backing up to the HTTPBuilder class as well.

My problem was I wanted to show the resulting XML but the RESTClient already parses the XML and returns that to the caller.

Jeffrey Erikson said...
This comment has been removed by the author.
Jeffrey Erikson said...

To see how I handled the output problem (I *really* wanted to use RESTClient at that point), check out this post:
Pretty-printing XML results returned from Groovy RESTClient

David Lopez said...

A more convenient solution, to continue using the restclient wrapper:

rest.handler.failure = rest.handler.succes