Home

Awesome

Add syntaxic sugar on top of vertx-lang-groovy

If you want to use Vert.x in Groovy with a less idiomatic but more groovy-ish API !

Have a look at the tests if you want to have a glimpse at how the sugar looks in action.

Enjoy as part of a healthy diet :)

Adding extensions

Since the projects relies on an Groovy extension module, you just have to add the jar to your project. Nothing more.

Example, if you're using Gradle:

repositories {
	jcenter()
}

dependencies {
	compile 'com.github.aesteve:grooveex:0.5'
}

And you're done. The new methods will also work with @TypeChecked since it's an extension module, not a metaclass.

Examples

Basic router, dealing with request / responses

Vertx vertx = Vertx.vertx

// Server-side
Router router = vertx.router
router['/hello'] >> { // router.get('hello').handler {
  def name = request.params['name'] // params is available through getParams => as a attribute
  response << "Hello $name"  // it.response().end("Hello $name"), routingContext is implicit, thus response is it.response()
}
server.requestHandler router.&accept
vertx.createHttpServer().listen()

// Client side
HttpClient client = vertx.createHttpClient()
HttpRequest req = client["/hello?name=World"] // client.get "/hello?name=World"
req >> { response -> // req.handler {
  response >>> { // response.bodyHandler
    assert it as String == 'Hello World' // as String => buffer.toString 'UTF-8'
  } 
} 
req++ // req.end()

Dealing with async results / futures

vertx.executeBlocking({ future ->
  try {
    sleep 1000
    future += 'completed !' // plus operator means complete('...'), plusplus operator means complete() 
  } catch(all) {
    future -= 'interrupted :(' // minus operator means fail
  }
}, { asyncResult ->
  if (asyncResult) { // asBoolean method overloading => you can use it in an if statement to check if succeeded() 
    println 'It has succeeded !'
  }
  boolean successful = +asyncResult && asyncResult.result == 'completed' // +asyncResult is asyncResult.succeeded() and getResult() is defined 
  boolean unsuccessful = -asyncResult || !asyncResult.result == 'completed' // -asyncResult is asyncResult.failed() and getResult() is defined
})

Pumping streams

Vertx vertx = Vertx.vertx

// Server-side
Router router = vertx.router
router['/pump'] >> {
  Buffer received = Buffer.buffer()
  request >> { buff -> // request handler
    received += buff // appends the received buffer (received << buff also works) 
  }
}
server.requestHandler router.&accept
vertx.createHttpServer().listen()

// Client side (sends a file to the server)
HttpClient client = vertx.createHttpClient()
HttpRequest req = client["/pump"]
vertx.fileSystem.open 'test-file', [:], { res ->
  AsyncFile file = res.result
  Pump filePump = file | req // createPump between readstream and writestream
  filePump++
}

Dealing with the event bus

Vertx vertx = Vertx.vertx
EventBus eb = vertx.eventBus
Buffer msg = 'Hello !' as Buffer // Buffer.buffer('Hello !')
eb['some-address'] >> { message -> // eb.consumer('some-address', { ... 
  println "Received : ${message.body}" // message.body()
}
eb['some-address'] << 'Hello !' // eb.send('some-address', 'Hello !')

Invoking an async service

router.get '/async' >> { context ->
  invokeAsyncMethod request.params['something'], fail | { result ->
    response << result
  }
}

Equivalent to :

router.get('/async').handler { context ->
  invokeAsyncMethod context.request().params['something'], { res ->
    if (res.failed()) {
      context.fail(res.cause())
    } else {
      response << res.result()
    }
  }
}

Rule of thumb

Basically :

Every getter method like a.response(), a.request() will also be available as an object attribute a.response, a.request (through a.getResponse() - Groovy convention -).

RouterBuilder

On top of the syntaxic sugar, you can use the RouterBuilder class to create a vertx-web router from a closure.

Just have a look at the routing file example to see how it looks like. It makes use of the overloaded operators like << or >> but also "wraps" Vert.x's handler closure to inject RoutingContext as delegate, so that you can directly write response.headers for instance and not it.response().headers. Every method available in RoutingContext will be directly available within your closure.

Keep in mind that the DSL doesn't force you to write all your handlers within the routingFile.

Vert.x defines a Groovy Handler (middleware) as either a class implementing the Handler interface, a closure or a method reference.

That means you can do the following :

RouterBuidler.make {
	get('/accessAnObject').handler(new MyHandler())
	get('/accessAMethod').handler(new HandlerWithMultipleMethods().&someMethod)
	get('/accessAClosure', SomeClass.aStaticClosure)
	get('/inline') {
		response.end << "or just inline stuff if it's simple enough" 
	}
}

And since you receive the Router instance once its built, you can use it programmatically as you're used to with Vert.x !

Split routing rules across multiple files

As soon as your application grows, you'll feel the need to separate concerns and the routing concerns separate. For instance the part dealing with static resources, server-side templates, could be kept together, whereas middlewares for REST-APIs should be kept in a separate file.

RouterBuilder builder = new RouterBuilder(vertx: vertx)
File routingFolder = new File('/some/path/routing_foler')
Router router = builder(routingFolder.listFiles().collect { it.name.endsWith('.groovy') })

Nesting routes

RouterBuilder builder = new RouterBuilder() 
Router router = builder {
  route('/blood') {
    // ...
    route('/sugar') {
      get {
        response << 'Yes please !'
      }
      post {
        String sent = body as String
        if (sent == 'I want that sugar sweet') {
          response << "Don't let nobody touch it"
        } else {
          fail 400
        }
      }
      route('/sex') {
        route('/magic') {
          blocking = true
          cors '*'
          get {
            response.headers['X-Song'] = 'Under the bridge'
            response.headers['X-Artist'] = 'RHCP'
            response.headers[HttpHeaders.DATE] = "Tue, 10 Mar 1992 12:45:26 GMT"
            response.chunked = true
            response += 'Is the city I live in...'
            sleep 1000
            response += '...The city of Angels'
            sleep 1000
            response << '- RHCP (1991)'
          }
        }
      }
    }
  }
}

Check/Expect

route('/expect') { // fails with 400, even if an exception is thrown (be careful !)
	expect { params['exists'] }
	expect { Long.valueOf params['long'] } // can throw NumberFormatException -> but 400 anyway
	expect { headers[AUTHORIZATION]?.indexOf('token ') == 0 }
	get {
		response << "everything's fine"
	}
}
route('/check') { // fails with the specified statusCode, and doesn't swallow exceptions
	check { params['token'] } | 401
	check { params['token'] == 'magic' } | 403
	get {
		response << "everything's fine"
	}
}

Payload & Marshalling

You can set a marshaller to read request body or write response body automatically simply by implementing the com.github.aesteve.vertx.groovy.io.Marshaller interface. By default, since Vert.x already uses Jackson, you can use the JacksonMarshaller we provided.

Then, just declare that your router (or route), uses this marshaller:

router {
    marshaller 'application/json', new JacksonMarshaller()
    // ...
}

Read request body using marshaller

router {
    marshaller 'application/json', new JacksonMarshaller()
    post('/create') {
        def marshalledBody = body as SomeObject // using 'as' on body will automatically invoke the marshaller
        // ...
    }
}

Write response body

router {
    marshaller 'application/json', new JacksonMarshaller()
    post('/create') {
        def marshalledBody = body as SomeObject
        def something = marshalledBody.transform() // do some stuff
        yield something // when you yield an object, it's set as response payload and calls context.next(), it will be marshalled automatically
    }
}

Register your own DSL item

Obviously we can't provide you a full list of useful middlewares, handlers. Moreover, you'll probably need a lot of stuff specific to your application.

Since it's pretty cool to read a routing file in a declarative way (like expect {something} for instance), you can register your own handlers/middlewares against RouterBuilder:

RouterBuilder builder = new RouterBuilder()
builder.extensions['injectHeader'] = { String headerName, String headerValue ->
	return { RoutingContext ctx ->
		response.headers[headerName] = headerValue
		ctx++
	}
}
builder {
	route('/dateHeader') {
		injectHeader 'Date', "${-> new Date() }" // lazy eval
		get {
			response << 'You should have the date header yay !"
		}
	}
}

Or if you prefer putting it into the routing file directly:

router {
	extension('injectHeader') { String headerName, String headerValue ->
		return { RoutingContext ctx ->
			response.headers[headerName] = headerValue
			ctx++
		}
	}
	route('/dateHeader') {
		injectHeader 'Date', "${-> new Date() }" // lazy eval
		get {
			response << 'You should have the date header yay !"
		}
	}
}

Matching multiple http methods

You can use either |, & or / operator to match multiple http methods. Just use the one that suits you the most.

router {
  route('/multi') {
    get | post {
      response << 'get or post'
    }
    options & delete {
      response << 'options or delete'
    }
    trace / connect {
      response << 'trace or connect'
    }
  }
}

Matchin every (declared) http methods

In case you want to match all the http methods you've (or will) declare in your route, you can use the keyword all.

router {
  route('/all') {
    all {
      response.chunked = true
      it++
    }
    get {
      response + 'get '
      it++
    }
    post {
      response + 'post '
      it++
    }
    all {
      response << 'Done'
    }
  }
}

Complete list of syntaxic sugar

WriteStream

Examples : ServerWebSocket, SockJSSocket, HttpClientRequest, HttpServerResponse

Groovy sugarVert.x standard
stream += datastream.write(data)
stream << datastream.end(data)

ReadStream

Examples : WebSocket, HttpServerRequest, ...

Groovy sugarVert.x standard
stream >> handlerstream.handler(handler)
stream >>> handlerstream.endHandler(handler)
`streamother`

Route

Groovy sugarVert.x standard
route >> handlerroute.handler(handler)
route ~/somePattern/route.pathRegex("somePattern")

Router

Groovy sugarVert.x standard
router['route'] = handlerrouter.route('route').handler(handler)

Buffer

Groovy sugarVert.x standard
buff << otherbuffer.appendBuffer(other)
buff << 'something'buffer.appendString('something')
buff << "hello $name"buffer.appendString("hello $name".toString()")
buff += otherbuffer.appendBuffer(other)
buff += 'something'buffer.appendString('something')
buff += "hello $name"buffer.appendString("hello $name".toString()")
buff as Stringbuffer.toString('UTF-8')

RoutingContext

Groovy sugarVert.x standard
ctx['key']ctx.get('key')
ctx['key'] = valuectx.put('key', value)
ctx.cookiesctx.cookies()
ctx.mountPointctx.mountPoint()
ctx.normalisedPathctx.normalisedPath()
ctx.responsectx.response()
ctx.requestctx.request()
ctx.sessionctx.session()
ctx.statusCodectx.statusCode()
ctx.redirect 'somewhere', statusCodectx.response().putHeader(HttpHeaders.LOCATION, 'somewhere').setStatusCode(statusCode).end()
ctx.userctx.user()
ctx.vertxctx.vertx()
ctx - 400ctx.fail(400)
ctx - new RuntimeException()ctx.fail(new RuntimeException())
ctx++ctx.next()
ctx >> closure{ res -> if (res.failed) { ctx.fail(res.cause()) } else { closure(res.result()) } }

Notes

NB : you already can call ctx++ (without this lib) since the method on RoutingContext is already called next()

NB : the last method is a very common pattern when you invoke an async method that takes an Handler<AsyncResult> as parameter. If it fails, you just want the context to fail, else, you'll need the result to do something with.

Additional methods

You'll find an ensure method within RoutingContext which is very useful for dealing with a common pattern in Vert.x.

Often, one of the first handlers in your routes will invoke an async service and :

In action :

router.get('/api/1/*').handler { ctx ->
    accessTokenChecker.check(ctx.request().params().get('accessToken'), {
        if (res.failed()) {
            ctx.fail res.cause()
        } else {
            User user = res.result()
            if (user && user.validated) {
                ctx.setUser(user)
                ctx.next()
            } else {
                ctx.fail 401
            }
        }
    })
}

Here's the ensure equivalent (buckle up !)

router.get('/api/1/').handler { ctx ->
    accessTokenChecker.check(ctx.request().params().get('accessToken'), ctx.ensure({ it && it.validated }) & { user = it } | 401)
}

Now let's benefit of full sugar and rewrite it :

router.get('/api/1/') >> {
    def accessToken = params['accessToken']
    def putTokenIfExistsOrFailWith401 = ensure({ it && it.validated }) & { user = it } | 401
    accessTokenChecker.check accessToken, putTokenIfExistsOrFailWith401 
}

This is especially useful for storing and composing small pieces of reusable logic into closures, like so :

def userHasValidAccount = { user ->
    user && user.validated
}
router.get('/api/1/') >> {
    def accessToken = params['accessToken']
    def putTokenIfExistsOrFailWith401 = ensure(userHasValidAccount) & { user = it } | 401
    accessTokenChecker.check accessToken, putTokenIfExistsOrFailWith401 
}

HttpServerRequest

Groovy sugarVert.x standard
req.paramsreq.params()
req.headersreq.headers()
req.methodreq.method()
req.pathreq.path()
req.remoteAddressreq.remoteAddress()
req - '/path'req.path() - '/path'
req >>> handlerreq.bodyHandler(handler)

HttpServerResponse

Groovy sugarVert.x standard
resp.headersresp.headers()
resp.headers = headersresp.headers().clear().addAll(headers)
resp++resp.end()
resp << new JsonBuilder(...)resp.end(new JsonBuilder(...).toString())

HttpClient

Groovy sugarVert.x standard
client['/path']client.get('/path')

HttpClientRequest

Groovy sugarVert.x standard
req++req.end()
req << new JsonBuilder(...)req.end(new JsonBuilder(...).toString())
req.methodreq.method()
req.headers = headersreq.headers().clear().addAll(headers)

HttpClientResponse

Groovy sugarVert.x standard
resp >>> handlerresp.bodyHandler(handler)
resp.headersresp.headers()

Message

Groovy sugarVert.x standard
msg.addressmsg.address()
msg.replyAddressmsg.replyAddress()
msg.headersmsg.headers()
msg.bodymsg.body()
msg << 'reply'msg.reply('reply')

AsyncResult

Groovy sugarVert.x standard
ar.failedar.failed()
-arar.failed()
ar.succeededar.succeeded()
+arar.succeeded()
ar.resultar.result()
ar.causear.cause()
if (ar) {if (ar?.succeeded()) {

Future

Groovy sugarVert.x standard
f += resultf.complete(result)
f -= causef.fail(cause)
f + resultf.complete(result)
f - causef.fail(cause)
f++f.complete()

Pump

Groovy sugarVert.x standard
pump++pump.start()
pump--pump.stop()

EventBus

Groovy sugarVert.x standard
eb['address'] >> handlereb.consumer('address', handler)
eb['address'] << msgeb.send(address, msg)
eb['address'] ** msgeb.publish(address, msg)

Vertx

Groovy sugarVert.x standard
Vertx.vertxVertx.vertx()
vertx.eventBusvertx.eventBus()
vertx.fileSystemvertx.fileSystem()
vertx.routerRouter.router(vertx)

String

Groovy sugarVert.x standard
'something' as BufferBuffer.buffer('something')

JsonBuilder

Groovy sugarVert.x standard
new JsonBuilder(...) as BufferBuffer.buffer(new JsonBuilder(...).toString())

Context

Groovy sugarVert.x standard
context.configcontext.config()

MultiMap

Groovy sugarVert.x standard
map[key]map.get(key)
map[key] = valuemap.put(key, value)
map -= keymap.remove(key)