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('ping') { 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/ping 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<PingModuleConfig> { @Override protected void configure() { bind(Ping) final Provider<Ping> 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<HandlerDecorator>) } }
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(['ping.path': 'pong']).build() } moduleConfig(PingModule, configData.get('/ping', 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/ping 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<Ping> pingProvider @Inject PingHandlerDecorator(Provider<Ping> 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<PingModuleConfig> { @Override protected void configure() { bind(Ping) bind(PingHandlerDecorator) } }
Written with Ratpack 1.1.1.