I am on a project where we have a couple of microservices talking to each other. One step of a microservice pipeline on our CI server is running smoke tests to check whether the service is up and behaves correctly. Recently I was working on such test and since we are using Spock in unit and integration tests I wanted to use tools provided by Groovy to write it.

There is groovyx.net.http.RESTClient that suits the case perfectly. You can call whatever HTTP method you would like using this class and it supports the most popular mime types used, i.e. XML, JSON, forms, plain text and streams. So I wrote the first draft of the test and encountered a small issue:

java.lang.IllegalArgumentException: No encoder found for request content type application/vnd.fooservice.v1+json

Although the data handled by the service is received and sent in JSON format the name of our mime type is not recognised by RESTClient. After a couple of minutes, I found the way to register the type. RESTClient is based on groovyx.net.http.HTTPBuilder that uses two registries for mime types:
groovyx.net.http.EncoderRegistry holding encoders taking a data to be sent and changing it to a specified format,
groovyx.net.http.ParserRegistry holding parsers producing real objects based on data received from a service.

How to register your own mime type there? It is quite easy. Here are the examples how to register mime type that actually uses JSON format:

RESTClient client = new RESTClient('http://fooservice.base.uri')
EncoderRegistry encoderRegistry = client.getEncoder()
encoderRegistry.putAt('application/vnd.fooservice.v1+json', new MethodClosure(encoderRegistry, 'encodeJSON'))
ParserRegistry parserRegistry = client.getParser()
parserRegistry.putAt('application/vnd.fooservice.v1+json', new MethodClosure(parserRegistry, 'parseJSON'))

How does it work? The client uses content type from request to find an encoder registered for it (in our example this is an instance of org.codehaus.groovy.runtime.MethodClosure). The closure calls a method specified as the second argument on an object provided as the first argument. This object above is EncoderRegistry since it supports encoding of JSON and other well-known formats. In case of data receiving the same thing is done with the difference a closure registered as parser uses logic defined in ParserRegistry to do the task.

But what to do if a mime type has a different format than the ones supported by both registries already? Well, you have to write your own encoder/parser. 😉

There is no need to implement a specific interface or extend some class. The only requirement is encoding/parsing methods should have arguments of concrete types specified. A quick check of encoding/parsing methods in both registries definition gives us enough information what they are.

For the encoder part a method can take one or two parameters: the data to be encoded and content type name which is optional and can be omitted. As a result it returns instance of org.apache.http.HttpEntity. Here is an example of such encoder:

class MyRequestEncoder {
    HttpEntity encodeRequest(Object model, Object contentType) {
        // parse model here
        // return entity instance
    }
}

In case of a parser method its argument is org.apache.http.HTTPResponse instance that holds whole response from called REST service. A type of parsed response can be anything – it depends on your use case:

class MyResponseParser {
    Object parseResponse(HttpResponse resp) {
        // parse response
        // return parsed value
    }
}

And here is an outline how registration and service calling can look like:

RESTClient client = new RESTClient('http://fooservice.base.uri')
EncoderRegistry encoder = client.getEncoder()
encoder.putAt('application/vnd.fooservice.v1+json',
new MethodClosure(new MyRequestEncoder(), 'encodeRequest'))
ParserRegistry parser = client.getParser()
parser.putAt('application/vnd.fooservice.v1+json',
new MethodClosure(new MyResponseParser(), 'parseResponse'))

// do call service
def response = client.post(
    path: ...,
    body: ...,
    contentType: 'application/vnd.fooservice.v1+json'
)

So as you can see it is pretty easy to provide a support of non standard mime type in RESTClient. 😉

Featured photo by Gina Zee on Unsplash

Categories: programming

0 Comments

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.