Ratpacked: Add Chains Via Registry
In a previous blog post we learned how to use HandlerDecorator.prepend
to add common handlers via the registry in our application. The type of handlers suitable for this approach were handlers that had common functionality for the rest of the handlers. If we want to add a Chain
implementation, containing handlers and maybe even path information, we cannot use the prepend
method, must write our own implementation of the HandlerDecorator
interface. This can be useful when we want to re-use a Chain
in multiple applications. We write a module that adds the Chain
implementation to the registry and we don't have to write any code in the handlers
section for the Chain
to work. This blog post is inspired by a conversation on the Ratpack Slack channel recently. First we create a simple handler that renders a result:
// File: src/main/groovy/com/mrhaki/ratpack/Ping.groovy
package com.mrhaki.ratpack
import ratpack.groovy.handling.GroovyChainAction
/**
* Implementation of a {@link ratpack.handling.Chain} interface
* by extending {@link GroovyChainAction}, so
* we can use Groovy DSL support in the
* {@link Ping#execute} method.
*/
class Ping extends GroovyChainAction {
@Override
void execute() throws Exception {
// What we normally would write
// in the handlers{} section
// of Ratpack.groovy.
path('pingpong') {
render 'Ratpack rules!'
}
}
}
First we register an instance of the Ping
class using the multiBindInstance
method in the bindings
section of our Ratpack.groovy
file:
import com.mrhaki.ratpack.Ping
import ratpack.handling.HandlerDecorator
import ratpack.handling.Handlers
import ratpack.registry.Registry
import static ratpack.groovy.Groovy.ratpack
ratpack {
bindings {
// We use multiBindInstance so multiple
// implementations of HandlerDecorator
// can be added to the registry.
// The HandlerDecorator is implemented with
// a closure. We add the Ping chain after
// other handlers (if set).
multiBindInstance(
HandlerDecorator, { Registry registry, Handler rest ->
Handlers.chain(rest, Handlers.chain(registry, new Ping()))
} as HandlerDecorator)
}
handlers {
get {
render 'Ratpack is awesome!'
}
}
}
If we start the application we get results when we invoke http://localhost:5050/
and http://localhost:5050/ping
:
$ http -b localhost:5050/
Ratpack is awesome!
$ http -b localhost:5050/pingpong
Ratpack rules!
$
Instead of using the multiBindInstance
method in the bindings
section we can create a module and register our Ping
class in the module:
// File: src/main/groovy/com/mrhaki/ratpack/PingModule.groovy
package com.mrhaki.ratpack
import com.google.inject.AbstractModule
import com.google.inject.multibindings.Multibinder
import ratpack.handling.Handler
import ratpack.handling.HandlerDecorator
import ratpack.handling.Handlers
import ratpack.registry.Registry
/**
* Module to register the {@link Ping}
* chain with the registry.
*/
class PingModule extends AbstractModule {
@Override
protected void configure() {
// We need the Multibinder, because other modules
// can also register HandlerDecorator implementations.
Multibinder
.newSetBinder(binder(), HandlerDecorator)
.addBinding()
.toInstance({ Registry registry, Handler rest ->
Handlers.chain(rest, Handlers.chain(registry, new Ping())
} as HandlerDecorator)
}
}
The Ratpack.groovy
file now is as follows:
// File: src/ratpack/Ratpack.groovy
import com.mrhaki.ratpack.Ping
import com.mrhaki.ratpack.PingModule
import static ratpack.groovy.Groovy.ratpack
ratpack {
bindings {
module PingModule
}
handlers {
get {
render 'Ratpack is awesome!'
}
}
}
To make the module more reusable we want to make the path binding for the Ping
handler configurable. So users can include the module, but still can set the path binding for their application. Things get a bit more complicated now and we need some changes in our code. First we write a class with the configuration data for the module. We only have one property path
that is used for the binding.
// File: src/main/groovy/com/mrhaki/ratpack/PingModuleConfig.groovy
package com.mrhaki.ratpack
class PingModuleConfig {
String path
}
Our Ping
class now accepts an instance of the configuration to get the path
value:
// File: src/main/groovy/com/mrhaki/ratpack/Ping.groovy
package com.mrhaki.ratpack
import ratpack.groovy.handling.GroovyChainAction
import javax.inject.Inject
/**
* Implementation of a {@link ratpack.handling.Chain} interface
* by extending {@link GroovyChainAction}, so
* we can use Groovy DSL support in the
* {@link Ping#execute} method.
*/
class Ping extends GroovyChainAction {
String pingPathBinding = 'ping'
@Inject
Ping(final PingModuleConfig config) {
pingPathBinding = config.path
}
@Override
void execute() throws Exception {
path(pingPathBinding) {
render 'Ratpack rules!'
}
}
}
Now we change the module and make it a ConfigurableModule
. Notice we bind the Ping
class so the dependency injection of our configuration works. Then we use a Provider
to get the fully configured instance of the Ping
class.
// File: src/main/groovy/com/mrhaki/ratpack/PingModule.groovy
package com.mrhaki.ratpack
import com.google.inject.Provider
import com.google.inject.multibindings.Multibinder
import ratpack.guice.ConfigurableModule
import ratpack.handling.Handler
import ratpack.handling.HandlerDecorator
import ratpack.handling.Handlers
import ratpack.registry.Registry
class PingModule extends ConfigurableModule {
@Override
protected void configure() {
bind(Ping)
final Provider pingProvider = getProvider(Ping)
Multibinder
.newSetBinder(binder(), HandlerDecorator)
.addBinding()
.toProvider({ ->
{ Registry registry, Handler rest ->
Handlers.chain(rest, Handlers.chain(registry, pingProvider.get()))
} as HandlerDecorator
} as Provider)
}
}
Finally we change our Ratpack application and configure the path to be pong
:
// File: src/ratpack/Ratpack.groovy
import com.mrhaki.ratpack.PingModule
import com.mrhaki.ratpack.PingModuleConfig
import ratpack.config.ConfigData
import static ratpack.groovy.Groovy.ratpack
ratpack {
bindings {
final ConfigData configData = ConfigData.of { builder ->
builder.props(['pingpong.path': 'pong']).build()
}
moduleConfig(PingModule, configData.get('/pingpong', PingModuleConfig))
}
handlers {
get {
render 'Ratpack is awesome!'
}
}
}
We can make our requests and look at the results:
$ http -b localhost:5050/
Ratpack is awesome!
$ http -b localhost:5050/pong
Ratpack rules!
$ http -b localhost:5050/pingpong
Client error 404
$
@guspower mentioned on the Ratpack Slack channel that we can also implement the HandlerDecorator
interface directly. This cleans up our module code. Here is the implementation:
// File: src/main/groovy/com/mrhaki/ratpack/PingHandlerDecorator.groovy
package com.mrhaki.ratpack
import com.google.inject.Provider
import ratpack.handling.Handler
import ratpack.handling.HandlerDecorator
import ratpack.handling.Handlers
import ratpack.registry.Registry
class PingHandlerDecorator implements HandlerDecorator {
/**
* Provider for Ping is automatically injected
* via the constructor.
*/
private final Provider pingProvider
@Inject
PingHandlerDecorator(Provider pingProvider) {
this.pingProvider = pingProvider
}
@Override
Handler decorate(Registry registry, Handler rest) throws Exception {
// Here is the code we had in the module before.
Handlers.chain rest, Handlers.chain(registry, pingProvider.get())
}
}
We now change the module class, which is even simpler now:
// File: src/main/groovy/com/mrhaki/ratpack/PingModule.groovy
package com.mrhaki.ratpack
import com.google.inject.Provider
import com.google.inject.multibindings.Multibinder
import ratpack.guice.ConfigurableModule
import ratpack.handling.Handler
import ratpack.handling.HandlerDecorator
import ratpack.handling.Handlers
import ratpack.registry.Registry
class PingModule extends ConfigurableModule {
@Override
protected void configure() {
bind(Ping)
bind(PingHandlerDecorator)
}
}
Written with Ratpack 1.1.1.