How to build a Java client to consume a RESTful Grails web service

Jul
24

Question posted on the Grails mailing list:

I'm trying to build a REST client for my REST server (which I build
with the help of the Definitive Guide To Grails) using RESTclient.
I'm using the following code at the server side:

def save = {
   def u = new Signup()
   u.properties = params
   if (u.save()) {
     render u as XML
   } else {
     render u.errors as XML
   }
}

and this piece of code at the client:

def signup = new RESTClient(signupsUrl)
def response = signup.post(
  contentType: XML,
  requestContentType: XML,
  body: {
    field(name:"surname", "Pascal")
    field(name:"infix", "de")
    field(name:"lastname", "Vink")
    field(name:"email", "pascal@example.com")
    field(name:"signupdate", params.signupdate)
  }
}
return response.data

Not only does it not put anything in the database, the response.data
is virtually unreadable, at least it's not XML as I would expect.
The following is a snippet of the output of println response.data:
emailSignupfalseorg.springframework.validation.FieldErrornullableSignup.email.nullable.error.Signup.emailSignup.email.nullable.error.emailSignup.email.nullable.error.java.lang.StringSignup.email.nullable.errorsignup.email.nullable.error.Signup.emailsignup.email.nullable.error.emailsignup.email.nullable.error.java.lang.Stringsignup.email.nullable.errorSignup.email.nullable.Signup.emailSignup.email.nullable.emailSignup.email.nullable.java.lang.StringSignup.email.nullablesignup.email.nullable.Signup.emailsignup.email.nullable.emailsignup.email.nullable.java.lang.Stringsignup.email.nullablenullable.Signup.emailnullable.emailnullable.java.lang.StringnullableProperty
[{0}] of class [{1}] cannot be
nullemailSignupinfixSignupfalseorg.springframework.validation.FieldErrornullable
and this goes on for a while (full print here http://gist.github.com/152217)
Does anyone know how to build a compatible client? And perhaps a
server that returns proper XML error messages?
Thanks in advance.
Pascal de Vink

My Answer:

Interestingly I spent a decent portion of this week fighting with similar problems! What I learned is that there are not many examples on the web about how to write more than the simplest restful clients, particularly for POST/PUT. Secondly that there's a difference between sending things as HTTP parameters and as content body. Most examples on the web use params - this is also how information is sent by your browser during an HTML form POST. In this case the content type application/x-www-form-urlencoded should be used. Below I have sent the data as XML inside the request content body, so I used text/xml as the content type. I’m not sure if there is a “best practice” but it seems to me that web services are better aligned to sending XML or JSON in the request content – that way you can post lists of objects or objects within objects.

The client which actually worked in the end, I wrote with Java and the Jersey library which was quite nice. You can do a post like this:

private static final String SERVICE_PREFIX = "http://localhost:8080/myapp/";

String myXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?><machine><model enumType=\"MachineModel\">Forklift</model><name>Big lifter</name></machine>";

Client client = Client.create();
WebResource webResource = client.resource(SERVICE_PREFIX + "machine/saveXml/3");
String response = webResource.type(MediaType.TEXT_XML).accept(MediaType.TEXT_XML).post(String.class, myXml);

System.out.println(response);

 Code in my Grails controller to serve the Web service:

def saveXml = {
  def xmlMachine = request.XML
  def machine = new Machine(name:xmlMachine.name.text(),
  model:MachineModel.valueOf(xmlMachine.model.text())).save()
  render machine as XML
}

Important things to note

  1. My example sends xml as content, not as name/value pairs in the params. That’s why I can access the XML with the call request.XML. If you send it as params there’s another handy call, I think request.params or something similar. You can also println(request) in the controller which gives you a bit more idea what’s going on... the value of Content-Length tells you how much content you sent.
  2. The value of request.XML – in my case xmlMachine – is confusing. If you print it or try to “cast” it as your target object, it doesn’t look like anything. But you can indeed access the xml with “.element.text()”.
  3. Lastly I’m using an Enum, hence the valueOf syntax. I think there may actually be a more convenient (1 call) way of creating the target object from the parsed XML but I think it might not have worked for me due to my Enum, so I just parse it manually.

If you're still having problems, break it down. Put some XML directly into your controller as a string and try and parse it with the Groovy XML Slurper, and send a response to the client. Once you've got that going, you can remove the hard-coded String and try and actually send the XML from the client.

4 comments

Tom Nichols (not verified)

Thanks for the helpful hints. I agree with the lack of good documentation; it is partly due to the lack of good public REST services to use as examples (or a lack of my awareness of them). My Twitter example (http://fisheye.codehaus.org/browse/gmod/httpbuilder/trunk/src/main/scrip...) provides a good example of using Twitter's REST interface. Unfortunately, as you alluded, these services use URL query parameters, not a request body for content creation (i.e. PUTs and POSTs).

Here's an example that should accomplish your example:

def client = new RESTClient( uri:'http://localhost:8080/myapp/',
contentType: 'text/xml' )

client.post( path : 'machine/saveXml/3',
body : {
machine {
model( enumType: 'MachineModel', 'Forklift' )
name 'Big lifter'
}
} )

If you want to send and receive raw XML you can do it like this:

def client = new RESTClient( uri:'http://localhost:8080/myapp/',
contentType: ContentType.TEXT,
headers: ['Content-Type':'text/xml', Accept:'text/xml'] )

def resp = client.post( path : 'machine/saveXml/3', body : myXml )
println resp.data.text

There may be some issue with response buffering (the RESTClient assumes the response stream is closed) but that will be fixed in the next RC release. Hope this is helpful. Generally if you only want to see the XML text for debugging purposes, it's probably easiest to turn on debug logging for httpclient, which will show you the request and response body (as well as headers) and can be switched off without
modifying your code.

Tom

John

There's another trick... Grails ignores the Accept header and instead determines the preferred content type from the content type of the request that was sent! Contrary to the doco of course. You can configure it to do otherwise with the following in the config:
grails.mime.use.accept.header=true
However you don't necessarily want to do that. According to Marc Palmer of the Grails mailing list, "Apparently the Accept header is very bad news as MS IE completely breaks this mechanism for content negotiation." So I leave it up to you to decide, dear reader.

Anonymous (not verified)

Hi all,

I have created a Grails web app that provides CRUD via rest using grails domain classes.
The domain class specifications therefore exist inside the server.

From java i can call the rest services and get the JSON/xml responses, but Im confused as to what the best way is to convert the response back in to corresponding Java domain objects is.

The only way I can see is to maintain both Java and groovy domain classes on each side and manually build java objects from the response

anyone any better ideas ?

John

Yes, you need to have the Groovy/Java domain objects on both sides in some way or other, either by maintaining them manually or having one common kind of shared store. I've never tried the latter between Groovy and Java so don't know if it would work.

For the actual serialisation/deserialisation, check out xstream. I find that a great tool for this. Although originally designed for XML, I think it might now work with JSON too. You have to put in a couple of config things to get it to work the same way as Grails serialises objects, but it's fairly easy to get going on the whole.

Post new comment

The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
4
i
y
K
Enter the code without spaces and pay attention to upper/lower case.