Ratpack has parsers to parse a request with a JSON body or a HTML form. We simply use the parse
method of Context
and Ratpack will check if there is a compliant parser in the registry. If there is a parser for that type available then Ratpack will parse the request and return a Promise
with the value. To write a new parser we need to implement the Parser
interface. The easiest way to implement this interface is by writing a class that extends ParserSupport
. Using the ParserSupport
class we can also work with an options object that a user can pass on to the parse
method of Context
. If we don't need options we can also extend the NoOptParserSupport
class.
Let's write a custom parser that can parse a request with a hex or base64 encoded value. The parser returns a String
object with the decoded value. In our example we also want the user to provide an optional options object of type StringParserOpts
which denotes the type of decoding:
// File: src/main/groovy/mrhaki/sample/StringParser.groovy package mrhaki.ratpack import ratpack.handling.Context import ratpack.http.TypedData import ratpack.parse.Parse import ratpack.parse.ParserSupport import ratpack.util.Types /** * Parser to decode hex or base64 values send * in the body of a request. */ class StringParser extends ParserSupport<StringParserOpts> { @Override def <T> T parse( final Context context, final TypedData body, final Parse<T, StringParserOpts> parse) throws Exception { // Check if type to be parsed can be handled by // this parser. We can also create a check based // on content type of the body for example. if (supportsType(parse.type)) { // Get request body that is either hex or // base64 encoded. final String bodyText = body.text // Get optional options. If the options are not set // a default instance is given. final StringParserOpts opts = parse.opts.orElse(StringParserOpts.hex()) // Check the options to see if hex or base64 decoding is needed. if (opts.hex) { return Types.cast(new String(bodyText.decodeHex())) } else if (opts.base64) { return Types.cast(new String(bodyText.decodeBase64())) } } // Cannot handle the type to be parsed. // Ratpack will try to find another match. return null } /** * Support String parsing. * * @param typeToken Type defined to be parsed. * @return True if type is String, false if not. */ private boolean supportsType(final typeToken) { typeToken.rawType == String } } /** * Class with options used to decode a value. * A user can provide an instance of this class using the * {@link Context#parse(java.lang.Class, java.lang.Object)} method. */ class StringParserOpts { private static enum Decoders { HEX, BASE64 } private Decoders decoder private StringParserOpts(final Decoders decoder) { this.decoder = decoder } static StringParserOpts hex() { new StringParserOpts(Decoders.HEX) } boolean isHex() { decoder == Decoders.HEX } static StringParserOpts base64() { new StringParserOpts(Decoders.BASE64) } boolean isBase64() { decoder == Decoders.BASE64 } }
We have the implementation of our parser, so now we write a specification to test it. We test the parser with a simple handler implementation that uses the parse
method and then simply renders the resulting String
value. In our specification we use RequestFixture
to invoke the handler and inspect the result:
// File: src/test/groovy/mrhaki/ratpack/StringParserSpec.groovy package mrhaki.ratpack import ratpack.handling.Handler import ratpack.test.handling.HandlingResult import ratpack.test.handling.RequestFixture import spock.lang.Specification class StringParserSpec extends Specification { void 'parse value in request body with StringParser using default decoder'() { given: final String content = 'Ratpack is gr8!'.bytes.encodeHex().toString() and: final Handler handler = { context -> context.parse(String) .then(context.&render) } when: final HandlingResult result = RequestFixture.handle(handler) { fixture -> fixture.body(content, 'text/plain') // Add StringParser to registry, so it can be used by Ratpack. .registry { registry -> registry.add(new StringParser()) } } then: result.rendered(String) == 'Ratpack is gr8!' } void 'parse hex value in request body with StringParser'() { given: final String content = 'Ratpack is gr8!'.bytes.encodeHex().toString() and: final Handler handler = { context -> // Parse and set options for hex decoding. context.parse(String, StringParserOpts.hex()) .then(context.&render) } when: final HandlingResult result = RequestFixture.handle(handler) { fixture -> fixture.body(content, 'text/plain') // Add StringParser to registry, so it can be used by Ratpack. .registry { registry -> registry.add(new StringParser()) } } then: result.rendered(String) == 'Ratpack is gr8!' } void 'parse base64 value in request body with StringParser'() { given: final String content = 'Ratpack is gr8!'.bytes.encodeBase64().toString() and: final Handler handler = { context -> // Parse and set options for base64 decoding. context.parse(String, StringParserOpts.base64()) .then(context.&render) } when: final HandlingResult result = RequestFixture.handle(handler) { fixture -> fixture.body(content, 'text/plain') // Add StringParser to registry, so it can be used by Ratpack. .registry { registry -> registry.add(new StringParser()) } } then: result.rendered(String) == 'Ratpack is gr8!' } }
Written with Ratpack 1.4.5.