Search

Dark theme | Light theme
Showing posts with label Ratpacked. Show all posts
Showing posts with label Ratpacked. Show all posts

June 7, 2017

Ratpacked: Assert No Exceptions Are Thrown With RequestFixture

Writing unit tests for our handlers in Ratpack is easy with RequestFixture. We invoke the handle method and use a Handler or Chain we want to test as argument. We can provide extra details on the fixture instance with a second argument, for example adding objects to the registry or setting the request method. The handle method returns a HandlingResult object. This object has the method exception that we can use to see if an exception occurred in our code under test. The method throws a HandlerExceptionNotThrownException if the expected exception doesn't occurr.

In the following example we have two feature methods to check if an exception occurred or not:

package sample

import ratpack.handling.Context
import ratpack.handling.Handler
import ratpack.test.handling.RequestFixture
import spock.lang.Specification

class HandlerSpec extends Specification {

    def 'check exception is thrown'() {
        given:
        def result = RequestFixture.handle new SampleHandler(true), Action.noop()

        expect:
        result.exception(Exception).message == 'Sample exception'
    }

    def 'check no exception is thrown'() {
        given:
        def result = RequestFixture.handle new SampleHandler(false), Action.noop()
        
        when:
        result.exception(Exception)

        then:
        thrown(HandlerExceptionNotThrownException)
    }
    
}

class SampleHandler implements Handler {
    
    /**
     * Indicate if we need to create an 
     * error with an exception or not.
     */
    private final boolean throwException = false

    SampleHandler(final boolean throwException) {
        this.throwException = throwException
    }

    @Override
    void handle(final Context ctx) throws Exception {
        if (throwException) {
            // Throw a sample exception.
            ctx.error(new Exception('Sample exception'))
            ctx.response.send()
        } else {
            // No exceptions.
            ctx.response.send('OK')
        }
    }
    
}

Instead of using the exception method of HandlingResult we can add a custom ServerErrorHandler to the fixture registry. Exceptions are handled by the error handler and we can check if an exception occurred or not via the error handler. In the following code we use a custom error handler:

package sample

import ratpack.error.ServerErrorHandler
import ratpack.handling.Context
import ratpack.handling.Handler
import ratpack.test.handling.RequestFixture
import spock.lang.Specification

class HandlerSpec extends Specification {

    /**
     * Error handler to capture exceptions.
     */
    private specErrorHandler = new SpecErrorHandler()

    /**
     * Add error handler as {@link ServerErrorHandler}
     * implementation to the fixture registry.
     */
    private fixtureErrorHandler = { fixture ->
        fixture.registry.add ServerErrorHandler, specErrorHandler
    }
    
    def 'check exception is thrown'() {
        when:
        RequestFixture.handle new SampleHandler(true), fixtureErrorHandler

        then:
        specErrorHandler.exceptionThrown()
        
        and:
        specErrorHandler.throwable.message == 'Sample exception'
    }

    def 'check no exception is thrown'() {
        when:
        RequestFixture.handle new SampleHandler(false), fixtureErrorHandler

        then:
        specErrorHandler.noExceptionThrown()
    }
    
}

class SampleHandler implements Handler {
    
    /**
     * Indicate if we need to create an 
     * error with an exception or not.
     */
    private final boolean throwException = false

    SampleHandler(final boolean throwException) {
        this.throwException = throwException
    }

    @Override
    void handle(final Context ctx) throws Exception {
        if (throwException) {
            // Throw a sample exception.
            ctx.error(new Exception('Sample exception'))
            ctx.response.send()
        } else {
            // No exceptions.
            ctx.response.send('OK')
        }
    }
    
}

/**
 * Simple implementation for {@link ServerErrorHandler}
 * where we simply store the original exception and 
 * add utility methods to determine if an exception is
 * thrown or not.
 */
class SpecErrorHandler implements ServerErrorHandler {
    
    /**
     * Store original exception.
     */
    private Throwable throwable

    /**
     * Store exception in {@link #throwable} and 
     * set response status to {@code 500}.
     * 
     * @param context Context for request.
     * @param throwable Exception thrown in code.
     * @throws Exception Something goes wrong.
     */
    @Override
    void error(final Context context, final Throwable throwable) throws Exception {
        this.throwable = throwable
        context.response.status(500)
    }

    /**
     * @return {@code true} if error handler is invoked, {@code false} otherwise.
     */
    boolean exceptionThrown() {
        throwable != null
    }

    /**
     * @return {@code true} if error handler is not invoked, {@code false} otherwise.
     */
    boolean noExceptionThrown() {
        !exceptionThrown()
    }
    
}

Written with Ratpack 1.4.5.

April 6, 2017

Ratpacked: Conditionally Map Or Flatmap A Promise

When we want to transform a Promise value we can use the map and flatMap methods. There are also variants to this methods that will only transform a value when a given predicate is true: mapIf and flatMapIf. We provide a predicate and function to the methods. If the predicate is true the function is invoked, otherwise the function is not invoked and the promised value is returned as is.

In the following example we have two methods that use the mapIf and flatMapIf methods of the Promise class:

// File: src/main/java/mrhaki/ratpack/NumberService.java
package mrhaki.ratpack;

import ratpack.exec.Promise;

public class NumberService {

    public Promise<Integer> multiplyEven(final Integer value) {
        return Promise.value(value)
                      .mapIf(number -> number % 2 == 0, number -> number * number);
    }
    
    public Promise<Integer> multiplyTens(final Integer value) {
        return Promise.value(value)
                      .flatMapIf(number -> number % 10 == 0, number -> multiplyEven(number));
    }
    
}

Now we take a look at the following specification to see the result of the methods with different input arguments:

// File: src/test/groovy/mrhaki/ratpack/NumberServiceSpec.groovy
package mrhaki.ratpack

import ratpack.test.exec.ExecHarness
import spock.lang.Specification
import spock.lang.Subject

class NumberServiceSpec extends Specification {

    @Subject
    private final numberService = new NumberService()

    void 'even numbers must be transformed with mapIf'() {
        when:
        final result = ExecHarness.yieldSingle {
            numberService.multiplyEven(startValue)
        }

        then:
        result.value == expected

        where:
        startValue || expected
        1          || 1
        2          || 4
        3          || 3
        4          || 16
    }

    void 'ten-th numbers must be transformed with flatMapIf'() {
        when:
        final result = ExecHarness.yieldSingle {
            numberService.multiplyTens(startValue)
        }

        then:
        result.value == expected

        where:
        startValue || expected
        1          || 1
        10         || 100
        2          || 2
        20         || 400
    }
}

Written with Ratpack 1.4.5.

April 3, 2017

Ratpacked: Get Time Taken To Fulfil Promise

The Promise class has a lot of methods. One of the methods is the time method. We can invoke this method an a Promise instance. The method creates a Duration object that we can use inside the method. The duration is the time taken from when the promise is subscribed to to when the result is available. The promise value is not changed, so we can add the time method at any position of a method chain for the Promise object.

In the following specification we check the duration for a Promise that is returned by the method generate of the class Numbers. For our example we wait for a number of seconds dependent on the argument of the generate method. In the specification we use the time method and check the time spent to fulfil the promise.

package mrhaki.ratpack

import ratpack.exec.Promise

import ratpack.test.exec.ExecHarness
import spock.lang.Specification
import spock.lang.Unroll

import java.time.Duration

class NumbersSpec extends Specification {

    @Unroll('with argument #num response time should be at least #responseTime')
    void 'time used by Numbers.generate method increased and dependent on argument'() {
        given:
        final numbers = new Numbers()

        and:
        long timer

        when:
        final result = ExecHarness.yieldSingle {
            numbers.generate(num)
                   .map { value -> value - 1 }
                   .time { duration -> timer = duration.toMillis() }
        }

        then:
        timer >= responseTime
        result.value == generateResult

        where:
        num | responseTime | generateResult
        1   | 1_000        | 0
        2   | 2_000        | 3
        10  | 10_000       | 99
    }

}

class Numbers  {
    Promise<Long> generate(final Long multiplier) {
        Promise
            .sync { -> 
                 // Wait for n-seconds...
                 sleep(multiplier * 1000)
                 multiplier
            }
            .map { value ->
                value * value 
            }
    }
}

Written with Ratpack 1.4.5.

March 23, 2017

Ratpacked: Add Ratpack To Spring Boot Application

In a previous post we saw how we can use Spring Boot in a Ratpack application. But the integration can also be the other way around: using Ratpack in a Spring Boot application. This way we can use Ratpack's power to handle requests sent to our Spring Boot application and still use all Spring Boot features in our application. The easiest way to add Ratpack to a Spring Boot application is adding a Ratpack dependency and use the @EnableRatpack annotation. With this annotation a RatpackServer instance is created and started along with configuration options.

Let's see an example Spring Boot application with Ratpack enabled. First we add Ratpack as dependency to our Spring Boot application. In our example we also add Ratpack's Dropwizard module as dependency. We use Gradle in our example:

// File: build.gradle
plugins {
    id 'groovy'
    id 'idea'
    id 'org.springframework.boot' version '1.5.2.RELEASE'
}

repositories {
    jcenter()
}

ext {
    ratpackVersion = '1.4.5'
}
dependencies {
    compile 'org.springframework.boot:spring-boot-starter'
    compile 'org.springframework.boot:spring-boot-devtools'
    
    // Add Ratpack for Spring Boot dependency.
    compile "io.ratpack:ratpack-spring-boot-starter:$ratpackVersion"
    // Add Dropwizard for Ratpack dependency.
    compile "io.ratpack:ratpack-dropwizard-metrics:$ratpackVersion"
    
    runtime 'ch.qos.logback:logback-classic:1.2.2'
    
    testCompile "org.spockframework:spock-core:1.0-groovy-2.4" 
}

springBoot {
    mainClass = 'mrhaki.sample.SampleApp'    
}

Now we look at our example application. We use the annotation @EnableRatpack to have Ratpack in our Spring Boot application. We add a Spring bean that implements Action<Chain>. Beans of this type are recognised by Spring Boot and are added to the Ratpack configuration. We also add a Spring bean that is a Ratpack module. This bean is also automatically added to the Ratpack configuration.

// File: src/main/java/mrhaki/sample/SampleApp.java
package mrhaki.sample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import ratpack.dropwizard.metrics.DropwizardMetricsConfig;
import ratpack.dropwizard.metrics.DropwizardMetricsModule;
import ratpack.func.Action;
import ratpack.handling.Chain;
import ratpack.handling.RequestLogger;
import ratpack.spring.config.EnableRatpack;
import ratpack.spring.config.RatpackServerCustomizer;

import java.time.Duration;

// Add Ratpack configuration for Spring Boot
@EnableRatpack
@EnableConfigurationProperties
@SpringBootApplication
public class SampleApp {

    /**
     * Start application.
     * 
     * @param args
     */
    public static void main(String[] args) {
        SpringApplication.run(SampleApp.class, args);
    }

    /**
     * Implementation for {@link MessageService} with pirate accent.
     * 
     * @return {@link MessageService} Pirate speak.
     */
    @Bean
    MessageService pirateMessage() {
        return name -> String.format("Arr, matey %s", name);
    }

    /**
     * Create Ratpack chain to handle requests to {@code /message} endpoint.
     * 
     * @return Ratpack chain.
     */
    @Bean
    Action<Chain> messageHandler() {
        return chain -> chain
                // Add logging for requests.
                .all(RequestLogger.ncsa())
                .get("message/:name?", ctx -> {
                    final String name = ctx.getPathTokens().getOrDefault("name", "mrhaki");
                    // Use MessageService implementation added to Spring context.
                    final String message = ctx.get(MessageService.class).message(name);
                    ctx.render(message);
                });
    }

    /**
     * Configuration properties to configure {@link DropwizardMetricsModule}.
     * Properties can be set via default Spring Boot mechanism like
     * environment variables, system properties, configuration files, etc.
     * 
     * @return Configuration for {@link DropwizardMetricsModule}
     */
    @Bean
    MetricsProperties metricsProperties() {
        return new MetricsProperties();
    }

    /**
     * Spring beans that are {@link com.google.inject.Module} objects are
     * automatically added to Ratpack's registry.
     * 
     * @param metricsProperties Configuration for module.
     * @return Module to add Dropwizard to Ratpack.
     */
    @Bean
    DropwizardMetricsModule metricsModule(final MetricsProperties metricsProperties) {
        // Create Dropwizard configuration.
        final DropwizardMetricsConfig config = new DropwizardMetricsConfig();
        if (metricsProperties.isJmx()) {
            config.jmx();
        }
        if (metricsProperties.getSlf4j().isEnabled()) {
            config.slf4j(slf4jConfig -> slf4jConfig
                    .enable(true)
                    .reporterInterval(Duration.ofSeconds(metricsProperties.getSlf4j().getInterval())));
        }

        // Create Dropwizard module.
        final DropwizardMetricsModule metricsModule = new DropwizardMetricsModule();
        metricsModule.setConfig(config);

        return metricsModule;
    }
}

We create a bean pirateMessage that implements the following interface:

package mrhaki.sample;

public interface MessageService {
    String message(final String name);
}

We also need the supporting class MetricsProperties to allow for configuration of the Dropwizard module.

// File: src/main/java/mrhaki/sample/MetricsProperties.java
package mrhaki.sample;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * Configuration for {@link ratpack.dropwizard.metrics.DropwizardMetricsModule}.
 */
@ConfigurationProperties(prefix = "dropwizard")
public class MetricsProperties {
    private boolean jmx;
    private Slf4Config slf4j = new Slf4Config();

    public boolean isJmx() {
        return jmx;
    }

    public void setJmx(final boolean jmx) {
        this.jmx = jmx;
    }

    public Slf4Config getSlf4j() {
        return slf4j;
    }

    public static class Slf4Config {
        private boolean enabled;
        private long interval = 30;

        public boolean isEnabled() {
            return enabled;
        }

        public void setEnabled(final boolean enabled) {
            this.enabled = enabled;
        }

        public long getInterval() {
            return interval;
        }

        public void setInterval(final long interval) {
            this.interval = interval;
        }
    }
}

To complete our application we also add a configuration file where we can change several aspects of our application:

# File: src/main/resources/application.yml
ratpack:
  port: 9000
---
dropwizard:
  jmx: true
  slf4j:
    enabled: true
    interval: 10

Let's start the application and we can see already in the logging output Ratpack is started:

$ ./gradlew bootRun
:compileJava
:compileGroovy NO-SOURCE
:processResources UP-TO-DATE
:classes
:findMainClass
:bootRun
06:38:45.137 [main] DEBUG org.springframework.boot.devtools.settings.DevToolsSettings - Included patterns for restart : []
06:38:45.139 [main] DEBUG org.springframework.boot.devtools.settings.DevToolsSettings - Excluded patterns for restart : [/spring-boot-starter/target/classes/, /spring-boot-autoconfigure/target/classes/, /spring-boot-starter-[\w-]+/, /spring-boot/target/classes/, /spring-boot-actuator/target/classes/, /spring-boot-devtools/target/classes/]
06:38:45.140 [main] DEBUG org.springframework.boot.devtools.restart.ChangeableUrls - Matching URLs for reloading : [file:/Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/ratpack/springboot/build/classes/main/, file:/Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/ratpack/springboot/build/resources/main/]

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.2.RELEASE)

2017-03-23 06:38:45.531  INFO 25302 --- [  restartedMain] mrhaki.sample.SampleApp                  : Starting SampleApp on mrhaki-laptop-2015.fritz.box with PID 25302 (/Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/ratpack/springboot/build/classes/main started by mrhaki in /Users/mrhaki/Projects/mrhaki.com/blog/posts/samples/ratpack/springboot)
2017-03-23 06:38:45.532  INFO 25302 --- [  restartedMain] mrhaki.sample.SampleApp                  : No active profile set, falling back to default profiles: default
2017-03-23 06:38:45.609  INFO 25302 --- [  restartedMain] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@701a7feb: startup date [Thu Mar 23 06:38:45 CET 2017]; root of context hierarchy
2017-03-23 06:38:46.023  INFO 25302 --- [  restartedMain] f.a.AutowiredAnnotationBeanPostProcessor : JSR-330 'javax.inject.Inject' annotation found and supported for autowiring
2017-03-23 06:38:46.687  INFO 25302 --- [  restartedMain] o.s.b.d.a.OptionalLiveReloadServer       : LiveReload server is running on port 35729
2017-03-23 06:38:46.714  INFO 25302 --- [  restartedMain] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2017-03-23 06:38:46.726  INFO 25302 --- [  restartedMain] ratpack.server.RatpackServer             : Starting server...
2017-03-23 06:38:46.963  INFO 25302 --- [  restartedMain] ratpack.server.RatpackServer             : Building registry...
2017-03-23 06:38:47.618  INFO 25302 --- [  restartedMain] ratpack.server.RatpackServer             : Initializing 1 services...
2017-03-23 06:38:47.711  INFO 25302 --- [  restartedMain] ratpack.server.RatpackServer             : Ratpack started for http://localhost:9000
2017-03-23 06:38:47.716  INFO 25302 --- [  restartedMain] mrhaki.sample.SampleApp                  : Started SampleApp in 2.556 seconds (JVM running for 2.992)

To add extra server configuration properties we must add a Spring bean that implements the ratpack.spring.config.RatpackServerCustomizer interface. The Spring Boot Ratpack configuration uses all beans found in the context that implement this interface. The interface has three methods we need to implement: getHandlers, getBindings and getServerConfig. The easiest way to implement the interface is by extending the class RatpackServerCustomizerAdapter. This class already provides empty implementations for the three methods. We only need to override the method we need in our application.

We rewrite our previous example application. We create a new class RatpackServerConfig that extends RatpackServerCustomizerAdapter. We override the method getServerConfig to set the development mode property of our Ratpack server configuration:

// File: src/main/java/mrhaki/sample/RatpackServerConfig.java
package mrhaki.sample;

import org.springframework.beans.factory.annotation.Autowired;
import ratpack.func.Action;
import ratpack.server.ServerConfigBuilder;
import ratpack.spring.config.RatpackProperties;
import ratpack.spring.config.RatpackServerCustomizerAdapter;

/**
 * Spring beans that implement {@link ratpack.spring.config.RatpackServerCustomizer}
 * interface our used for configuring Ratpack. The class
 * {@linly onk RatpackServerCustomizerAdapter} is a convenience class we can 
 * extend and only override the methods we need to.
 */
public class RatpackServerConfig extends RatpackServerCustomizerAdapter {

    /**
     * {@link RatpackProperties} configuration properties 
     * for Ratpack configuration.
     */
    private final RatpackProperties ratpack;

    public RatpackServerConfig(final RatpackProperties ratpack) {
        this.ratpack = ratpack;
    }

    /**
     * Extra configuration for the default Ratpack server configuration.
     * 
     * @return Extra server configuration.
     */
    @Override
    public Action<ServerConfigBuilder> getServerConfig() {
        return serverConfigBuilder -> serverConfigBuilder
                .development(ratpack.isDevelopment());
    }
    
}

We change SampleApp and add RatpackServerConfig as Spring bean:

// File: src/main/java/mrhaki/sample/SampleApp.java
...
    /**
     * Extra Ratpack server configuration.
     * 
     * @param ratpackProperties Properties for Ratpack server configuration.
     * @return Bean with extra Ratpack server configuration.
     */
    @Bean
    RatpackServerCustomizer ratpackServerSpec(final RatpackProperties ratpackProperties) {
        return new RatpackServerConfig(ratpackProperties);
    }
...

Another rewrite of our application could be to move all Ratpack configuration like handlers and bindings in the RatpackServerConfig class. We simply need to override the other two methods: getHandlers and getBindings. This way we have all the configuration together.

// File: src/main/java/mrhaki/sample/RatpackServerConfig.java
package mrhaki.sample;

import ratpack.dropwizard.metrics.DropwizardMetricsConfig;
import ratpack.dropwizard.metrics.DropwizardMetricsModule;
import ratpack.func.Action;
import ratpack.guice.BindingsSpec;
import ratpack.handling.Chain;
import ratpack.handling.RequestLogger;
import ratpack.server.ServerConfigBuilder;
import ratpack.spring.config.RatpackProperties;
import ratpack.spring.config.RatpackServerCustomizerAdapter;

import java.time.Duration;
import java.util.Arrays;
import java.util.List;

/**
 * Spring beans that implement {@link ratpack.spring.config.RatpackServerCustomizer}
 * interface our used for configuring Ratpack. The class
 * {@linly onk RatpackServerCustomizerAdapter} is a convenience class we can 
 * extend and only override the methods we need to.
 */
public class RatpackServerConfig extends RatpackServerCustomizerAdapter {

    /**
     * {@link RatpackProperties} configuration properties 
     * for Ratpack configuration. 
     */
    private final RatpackProperties ratpack;

    /**
     * Configuration properties for {@link DropwizardMetricsModule}.
     */
    private final MetricsProperties metrics;

    public RatpackServerConfig(
            final RatpackProperties ratpack, 
            final MetricsProperties metrics) {
        this.ratpack = ratpack;
        this.metrics = metrics;
    }

    /**
     * Extra configuration for the default Ratpack server configuration.
     * 
     * @return Extra server configuration.
     */
    @Override
    public Action<ServerConfigBuilder> getServerConfig() {
        return serverConfigBuilder -> serverConfigBuilder
                .development(ratpack.isDevelopment());
    }

    /**
     * Configure Ratpack handlers.
     * 
     * @return List of Ratpack chain configurations.
     */
    @Override
    public List<Action<Chain>> getHandlers() {
        return Arrays.asList(messageHandler()); 
    }

    /**
     * Create Ratpack chain to handle requests to {@code /message} endpoint.
     *
     * @return Ratpack chain.
     */
    private Action<Chain> messageHandler() {
        return chain -> chain
                // Add logging for requests.
                .all(RequestLogger.ncsa())
                .get("message/:name?", ctx -> {
                    final String name = ctx.getPathTokens().getOrDefault("name", "mrhaki");
                    // Use MessageService implementation added to Spring context.
                    final String message = ctx.get(MessageService.class).message(name);
                    ctx.render(message);
                });
    }

    /**
     * Add {@link DropwizardMetricsModule} to the Ratpack bindings.
     * 
     * @return Ratpack bindings.
     */
    @Override
    public Action<BindingsSpec> getBindings() {
        return bindings -> {
            bindings.module(DropwizardMetricsModule.class, dropwizardMetricsConfig());
        };
    }

    /**
     * Configuration for {@link DropwizardMetricsModule}.
     * 
     * @return Configuration action for configuring {@link DropwizardMetricsModule}.
     */
    private Action<DropwizardMetricsConfig> dropwizardMetricsConfig() {
        return config -> {
            if (metrics.isJmx()) {
                config.jmx();
            }
            if (metrics.getSlf4j().isEnabled()) {
                config.slf4j(slf4jConfig -> slf4jConfig
                        .enable(true)
                        .reporterInterval(Duration.ofSeconds(metrics.getSlf4j().getInterval())));
            }
        };
    }
}

Written with Ratpack 1.4.5 and Spring Boot 1.5.2.RELEASE.

March 8, 2017

Ratpacked: Override Registry Objects With Mocks In Integration Specifications

Testing a Ratpack application is not difficult. Ratpack has excellent support for writing unit and integration tests. When we create the fixture MainClassApplicationUnderTest we can override the method addImpositions to add mock objects to the application. We can add them using the ImpositionsSpec object. When the application starts with the test fixture the provided mock objects are used instead of the original objects. For a Groovy based Ratpack application we can do the same thing when we create the fixture GroovyRatpackMainApplicationUnderTest.

We start with a simple Java Ratpack application. The application adds an implementation of a NumberService interface to the registry. A handler uses this implementation for rendering some output.

// File: src/main/java/mrhaki/ratpack/RatpackApplication.java
package mrhaki.ratpack;

import ratpack.registry.Registry;
import ratpack.server.RatpackServer;

public class RatpackApplication {
    public static void main(String[] args) throws Exception {
        RatpackServer.start(server -> server
                // Add a implementation of NumberService interface
                // to the registry, so it can be used by the handler.
                .registry(Registry.single(NumberService.class, new NumberGenerator()))
                
                // Register a simple handler to get the implementation
                // of the NumberService interface, invoke the give() method
                // and render the value.
                .handler(registry -> ctx -> ctx
                        .get(NumberService.class).give()
                        .then(number -> ctx.render(String.format("The answer is: %d", number)))));
    }
}

The NumberService interface is not difficult:

// File: src/main/java/mrhaki/ratpack/NumberService.java
package mrhaki.ratpack;

import ratpack.exec.Promise;

public interface NumberService {
    Promise<Integer> give();
}

The implementation of the NumberService interface returns a random number:

// File: src/main/java/mrhaki/ratpack/NumberGenerator.java
package mrhaki.ratpack;

import ratpack.exec.Promise;

import java.util.Random;

public class NumberGenerator implements NumberService {
    @Override
    public Promise<Integer> give() {
        return Promise.sync(() -> new Random().nextInt());
    }
}

To test the application we want to use a mock for the NumberService interface. In the following specification we override the addImpositions method of the MainClassApplicationUnderTest class:

// File: src/test/groovy/mrhaki/ratpack/RatpackApplicationSpec.groovy
package mrhaki.ratpack

import ratpack.exec.Promise
import ratpack.impose.ImpositionsSpec
import ratpack.impose.UserRegistryImposition
import ratpack.registry.Registry
import ratpack.test.MainClassApplicationUnderTest
import ratpack.test.http.TestHttpClient
import spock.lang.AutoCleanup
import spock.lang.Specification

/**
 * Integration test for the application.
 */
class RatpackApplicationSpec extends Specification {
    
    /**
     * Mock implementation for the {@link NumberService}.
     */
    private final NumberService mockNumberService = Mock()

    /**
     * Setup {@link RatpackApplication} for testing and provide
     * the {@link #mockNumberService} instance to the Ratpack registry.
     */
    @AutoCleanup
    private aut = new MainClassApplicationUnderTest(RatpackApplication) {
        @Override
        protected void addImpositions(final ImpositionsSpec impositions) {
            // Set implementation of NumberService interface to
            // our mock implementation for the test.
            impositions.add(
                    UserRegistryImposition.of(
                            Registry.single(NumberService, mockNumberService)))
        }
    }

    /**
     * Use HTTP to test our application.
     */
    private TestHttpClient httpClient = aut.httpClient
    
    void 'render output with number'() {
        when:
        final response = httpClient.get()
        
        then:
        // Our mock should get invoked once and we return 
        // the fixed value 42 wrapped in a Promise.
        1 * mockNumberService.give() >> Promise.sync { 42 }

        and:
        response.statusCode == 200
        response.body.text == 'The answer is: 42'
    }
}

Written with Ratpack 1.4.5.

Ratpacked: Combine Groovy DSL With RatpackServer Java Configuration

We have several options to define a Ratpack application. We can use a Java syntax to set up the bindings and handlers. Or we can use the very nice Groovy DSL. It turns out we can use both together as well. For example we can define the handlers with the Groovy DSL and the rest of the application definition is written in Java. To combine both we start with the Java configuration and use the bindings and handlers method of the Groovy.Script class to inject the files with the Groovy DSL.

We start with a sample application where we use Java configuration to set up our Ratpack application:

// File: src/main/java/mrhaki/ratpack/Application.java
package mrhaki.ratpack;

import ratpack.func.Action;
import ratpack.handling.Chain;
import ratpack.handling.Handler;
import ratpack.registry.RegistrySpec;
import ratpack.server.RatpackServer;

import java.util.Optional;

public class Application {

    public static void main(String[] args) throws Exception {
        new Application().startServer();
    }

    void startServer() throws Exception {
        RatpackServer.start(server -> server
                .registryOf(registry())
                .handlers(chain()));
    }

    private Action<RegistrySpec> registry() {
        return registry -> registry
                .add(new RecipeRenderer())
                .add(RecipeRepository.class, new RecipesList());
    }

    private Action<Chain> chain() {
        return chain -> chain.post("recipe", recipeHandler());
    }

    private Handler recipeHandler() {
        return ctx -> ctx
                .parse(RecipeRequest.class)
                .flatMap(recipeRequest -> ctx
                        .get(RecipeRepository.class)
                        .findRecipeByName(recipeRequest.getName()))
                .then((Optional<Recipe> optionalRecipe) -> ctx.render(optionalRecipe));
    }

}

We can use the Groovy DSL for the bindings and handlers definitions and use them in our Java class with the Groovy.Script class. First we create the files bindings.groovy and handlers.groovy in the directory src/main/resources so they will be in the class path of the Java application. We can use the Groovy DSL syntax in the files:

// File: src/main/resources/bindings.groovy
import mrhaki.ratpack.RecipeRenderer
import mrhaki.ratpack.RecipeRepository
import mrhaki.ratpack.RecipesList

import static ratpack.groovy.Groovy.ratpack

ratpack {
    bindings {
        add new RecipeRenderer()
        add RecipeRepository, new RecipesList()
    }   
}
// File: src/main/resources/handlers.groovy
import mrhaki.ratpack.Recipe
import mrhaki.ratpack.RecipeRepository
import mrhaki.ratpack.RecipeRequest

import static ratpack.groovy.Groovy.ratpack

ratpack {
    handlers {
        post('recipe') { RecipeRepository recipeRepository ->
            parse(RecipeRequest)
                    .flatMap { RecipeRequest recipeRequest -> 
                        recipeRepository.findRecipeByName(recipeRequest.name) 
                    }
                    .then { Optional<Recipe> optionalRecipe -> 
                        render(optionalRecipe) 
                    }
        }
    }
}

We have our Groovy DSL files with the definitions. To use them in our Java code to define the Ratpack application we must make sure Ratpack can find them. Therefore we create an empty marker file .ratpack in src/main/resources. With this file in place we can use Ratpack's findBaseDir method to set the base directory for finding external files. It is time to refactor our application:

// File: src/main/java/mrhaki/ratpack/Application.java
package mrhaki.ratpack;

import ratpack.func.Action;
import ratpack.groovy.Groovy;
import ratpack.handling.Chain;
import ratpack.handling.Handler;
import ratpack.registry.RegistrySpec;
import ratpack.server.RatpackServer;

import java.util.Optional;

public class Application {

    public static void main(String[] args) throws Exception {
        new Application().startServer();
    }

    void startServer() throws Exception {
        RatpackServer.start(server -> server
                // Set base dir with directory that
                // contains marker file .ratpack.
                .serverConfig(config -> config.findBaseDir())
                // Use bindings.groovy with static compilation.
                .registry(Groovy.Script.bindings(true))
                // Use handlers.groovy with static compilation.
                .handler(Groovy.Script.handlers(true)));
    }

}

Written with Ratpack 1.4.5.

March 7, 2017

Ratpacked: Type Check And Static Compilation For Groovy DSL

One of the very nice features of Ratpack is the Groovy DSL to define our application. We get a nice DSL to set up the registry, to define handlers and more. Because of clever use of the @DelegateTo annotation we get good code completion in our IDE. We can also add static compilation of our Groovy DSL when we start our Ratpack application. With static compilation the script is type checked at compile time so we get earlier feedback on possible errors in the script. To configure static compilation we must invoke the app method of the Groovy.Script class with the argument true.

We start with a Groovy DSL for an application that serves recipes. Notice the Closure arguments are all typed, so with type checking there are no errors.

// File: src/ratpack/ratpack.groovy
import mrhaki.ratpack.Recipe
import mrhaki.ratpack.RecipeRenderer
import mrhaki.ratpack.RecipeRepository
import mrhaki.ratpack.RecipeRequest
import mrhaki.ratpack.RecipesList

import static ratpack.groovy.Groovy.ratpack

ratpack {
    bindings {
        add new RecipeRenderer()
        add RecipeRepository, new RecipesList()
    }

    handlers {
        post('recipe') { RecipeRepository recipeRepository ->
            parse(RecipeRequest)
                    .flatMap { RecipeRequest recipeRequest -> 
                        recipeRepository.findRecipeByName(recipeRequest.name) 
                    }
                    .then { Optional<Recipe> optionalRecipe -> 
                        render(optionalRecipe) 
                    }
        }
    }
}

Next we create a class with a main method that will be the starting point of our application. We need to run this class to start our Ratpack application. The example class is a Java class, but could also be written with Groovy:

// File: src/main/java/mrhaki/sample/GroovyCompileStaticRatpackMain.java
package mrhaki.ratpack;

import ratpack.groovy.Groovy;
import ratpack.server.RatpackServer;

import java.util.Optional;

public class GroovyCompileStaticRatpackMain {
    
    public static void main(String[] args) throws Exception {
        RatpackServer.start(Groovy.Script.app(true /* compileStatic */));
    }
}

When we use the Gradle Ratpack plugin we use this class as the main class:

// File: build.gradle
...
mainClassName = 'mrhaki.ratpack.GroovyCompileStaticRatpackMain'
...

Now we can still use the run task to start our Ratpack application.

Written with Ratpack 1.4.5.

Ratpacked: Implement A Custom Request Parser

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.

Ratpacked: Implement Custom Rendering With Renderable Interface

Ratpack uses renderers to render output. We can create our own renderer by implementing the Renderer interface. The renderer class needs to implement a render method that has the object we want to render as argument. Alternatively we can add the logic to render a object to the class definition of that object. So instead of having a separate renderer class for a class, we add the render logic to the class itself. To achieve this we must implement the Renderable interface for our class. Ratpack provides a RenderableRenderer in the registry that knows how to render classes that implement the Renderable interface.

In the following example we have a Recipe class that implements the Renderable interface:

// File: src/main/java/mrhaki/ratpack/Recipe.java
package mrhaki.ratpack;

import ratpack.handling.Context;
import ratpack.render.Renderable;

import static ratpack.jackson.Jackson.json;

public class Recipe implements Renderable {
    
    private final String name;

    public Recipe(final String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    /**
     * Render object as JSON.
     * 
     * @param context Ratpack context.
     */
    @Override
    public void render(final Context context) throws Exception {
        context.byContent(content -> content
                .plainText(() -> context.render(this.toString()))
                .json(() -> context.render(json(this))));
    }
    
    public String toString() {
        return String.format("Recipe::name=%s", this.name);
    }
}

Let's write a specification to test how the Recipe is rendered:

// File: src/test/groovy/mrhaki/ratpack/RecipeRenderableSpec.groovy
package mrhaki.ratpack

import groovy.json.JsonSlurper
import ratpack.test.embed.EmbeddedApp
import spock.lang.Specification

class RecipeRenderableSpec extends Specification {
    
    def app = EmbeddedApp.fromHandler { ctx ->
        ctx.render(new Recipe('macaroni'))
    }
    
    def httpClient = app.httpClient
    
    void 'render Recipe as plain text'() {
        when:
        def response = httpClient.requestSpec { request -> 
            request.headers.set 'Accept', 'text/plain'
        }.get()
        
        then:
        response.statusCode == 200
        
        and:
        response.body.text == 'Recipe::name=macaroni'
    }

    void 'render Recipe as JSON'() {
        when:
        def response = httpClient.requestSpec { request ->
            request.headers.set 'Accept', 'application/json'
        }.get()

        then:
        response.statusCode == 200

        and:
        def recipe = new JsonSlurper().parseText(response.body.text)
        recipe.name == 'macaroni'
    }
}

Written with Ratpack 1.4.5.

March 6, 2017

Ratpacked: Render Optional Type Instance

Ratpack uses renderers to render objects with the render method of the Context class. Ratpack has several renderers that are available automatically. One of those renderers is the OptionalRenderer. When we want to render an Optional object this renderer is selected by Ratpack. If the Optional instance has a value the value is passed to the render method. If the value is not present a 404 client error is returned.

In the following example application we have a RecipeRepository class with a findRecipeByName method. This method returns Promise<Optional<Recipe>>:

// File: src/main/java/mrhaki/ratpack/RecipeRepository.java
package mrhaki.ratpack;

import ratpack.exec.Promise;

import java.util.Optional;

public interface RecipeRepository {
    Promise<Optional<Recipe>> findRecipeByName(final String name);
}

We have a Handler that will use the findRecipeByName method and then render the Optional<Recipe> object. The following example application shows the handler implementation:

// File: src/main/java/mrhaki/ratpack/Application.java
package mrhaki.ratpack;

import ratpack.func.Action;
import ratpack.handling.Chain;
import ratpack.handling.Handler;
import ratpack.registry.RegistrySpec;
import ratpack.server.RatpackServer;

import java.util.Optional;

public class Application {

    public static void main(String[] args) throws Exception {
        new Application().startServer();
    }
    
    void startServer() throws Exception {
        RatpackServer.start(server -> server
                .registryOf(registry())
                .handlers(chain()));
    }
    
    private Action<RegistrySpec> registry() {
        return registry -> registry
                .add(new RecipeRenderer())
                .add(RecipeRepository.class, new RecipesList());
    }

    private Action<Chain> chain() {
        return chain -> chain.post("recipe", recipeHandler());
    }

    private Handler recipeHandler() {
        return ctx -> ctx
                .parse(RecipeRequest.class)
                .flatMap(recipeRequest -> ctx
                        .get(RecipeRepository.class)
                        .findRecipeByName(recipeRequest.getName()))
                .then((Optional<Recipe> optionalRecipe) -> ctx.render(optionalRecipe));
    }

}

The application also uses a custom RecipeRenderer. This renderer is used when the Optional<Recipe> has a value:

// File: src/main/java/mrhaki/ratpack/RecipeRenderer.java
package mrhaki.ratpack;

import ratpack.handling.Context;
import ratpack.render.RendererSupport;

import static ratpack.jackson.Jackson.json;

public class RecipeRenderer extends RendererSupport<Recipe> {
    @Override
    public void render(final Context ctx, final Recipe recipe) throws Exception {
        ctx.render(json(recipe));
    }
}

Let's write a specification where we can test that a client error with status code 404 is returned when the Optional is empty. Otherwise the actual value is rendered:

// File: src/test/groovy/mrhaki/ratpack/ApplicationSpec.groovy
package mrhaki.ratpack

import groovy.json.JsonSlurper
import ratpack.exec.Promise
import ratpack.http.MediaType
import ratpack.impose.ImpositionsSpec
import ratpack.impose.UserRegistryImposition
import ratpack.registry.Registry
import ratpack.test.MainClassApplicationUnderTest
import spock.lang.Specification
import spock.lang.Subject

import static groovy.json.JsonOutput.toJson

class ApplicationSpec extends Specification {
    
    private RecipeRepository recipeMock = Mock()
    
    @Subject
    private aut = new MainClassApplicationUnderTest(Application) {
        @Override
        protected void addImpositions(final ImpositionsSpec impositions) {
            // Add mock for RecipeRepository.
            impositions.add(UserRegistryImposition.of(Registry.of { registry ->
                registry.add(RecipeRepository, recipeMock)
            }))
        }
    }
    
    private httpClient = aut.httpClient
    
    void 'response status 404 when Optional<Recipe> is empty'() {
        when:
        def response = httpClient.requestSpec { requestSpec ->
            requestSpec.headers.set 'Content-type', MediaType.APPLICATION_JSON
            requestSpec.body { body ->
                body.text(toJson(name: 'sushi'))
            }
        }.post('recipe')
        
        then:
        1 * recipeMock.findRecipeByName('sushi') >> Promise.value(Optional.empty())
        
        and:
        response.statusCode == 404
    }

    void 'render Recipe when Optional<Recipe> is not empty'() {
        when:
        def response = httpClient.requestSpec { requestSpec ->
            requestSpec.headers.set 'Content-type', MediaType.APPLICATION_JSON
            requestSpec.body { body ->
                body.text(toJson(name: 'macaroni'))
            }
        }.post('recipe')

        then:
        1 * recipeMock.findRecipeByName('macaroni') >> Promise.value(Optional.of(new Recipe('macaroni')))

        and:
        response.statusCode == 200
        
        and:
        def recipe = new JsonSlurper().parseText(response.body.text)
        recipe.name == 'macaroni'
    }

}

Written with Ratpack 1.4.5.

March 2, 2017

Ratpacked: Using Spring Cloud Contract As Client

In a previous post we learned about Spring Cloud Contract. We saw how we can use contracts to implement the server side of the contract. But Spring Cloud Contract also creates a stub based on the contract. The stub server is implemented with Wiremock and Spring Boot. The server can match incoming requests with the contracts and send back the response as defined in the contract. Let's write an application that is invoking HTTP requests on the server application we wrote before. In the tests that we write for this client application we use the stub that is generated by Spring Cloud Contract. We know the stub is following the contract of the actual server.

First we create the stub in our server project with the Gradle task verifierStubsJar. The tests in the client application need these stub and will fetch it as dependency from a Maven repository or the local Maven repository. For our example we use the local Maven repository. We add the maven-publish plugin to the server project and run the task publishToMavenLocal.

We create a new Gradle project for our Ratpack application that is invoking requests on the pirate service. The following Gradle build file sets all dependencies for the application and plugins to run and test the application:

plugins {
    id 'groovy'
    id 'project-report'
    id 'io.ratpack.ratpack-java' version '1.4.5'
    id 'com.github.johnrengelman.shadow' version '1.2.4'
    id 'io.spring.dependency-management' version '1.0.0.RELEASE'
}

group = 'mrhaki.ratpack.pirate.client'
version = '0.0.1'

repositories {
    jcenter()
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-dependencies:Camden.SR5"
        mavenBom "org.springframework.boot:spring-boot-starter-parent:1.5.1.RELEASE"
    }
    dependencies {
        dependency 'com.google.guava:guava:19.0'
    }
}

dependencies {
    runtime 'org.slf4j:slf4j-simple:1.7.24'
    
    testCompile 'org.codehaus.groovy:groovy-all:2.4.9'
    testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'
    testCompile 'org.spockframework:spock-spring:1.0-groovy-2.4'

    testCompile 'org.springframework.boot:spring-boot-starter-web', {
        exclude module: 'logback-classic'
    }
    testCompile 'org.springframework.cloud:spring-cloud-starter-contract-stub-runner', {
        exclude module: 'logback-classic'
    }
    testRuntime 'javax.servlet:javax.servlet-api:3.1.0'
}

mainClassName = 'mrhaki.sample.TavernApp'

assemble.dependsOn shadowJar

The code for the application can be found in Github. We write a test for our application and use the Spring Cloud Contract generated stub server. In our application we use the HttpClient class from Ratpack to invoke the pirate service. These calls will be send to the stub server in the specification. To start the stub we use the JUnit rule StubRunnerRule. We configure it to use the Maven local repository and define the dependency details. The stub server starts using a random port and we can get the address with the method findStubUrl. In our Ratpack application we have the address of the pirate service in the registry. We use impositions in our tests to replace that address with the stub server address:

package mrhaki.sample

import org.junit.ClassRule
import org.springframework.cloud.contract.stubrunner.junit.StubRunnerRule
import ratpack.impose.ImpositionsSpec
import ratpack.impose.UserRegistryImposition
import ratpack.registry.Registry
import ratpack.test.MainClassApplicationUnderTest
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

class BartenderSpec extends Specification {

    @ClassRule
    @Shared
    private StubRunnerRule mockServer =
            new StubRunnerRule()
                    .downloadStub('mrhaki.ratpack.pirate.service', 'pirate-service', '0.0.2', 'stubs')
                    .workOffline(true)  // Use Maven local repo

    @AutoCleanup
    @Shared
    private app = new MainClassApplicationUnderTest(TavernApp) {
        @Override
        protected void addImpositions(final ImpositionsSpec impositions) {
            final mockUrl = mockServer.findStubUrl('mrhaki.ratpack.pirate.service', 'pirate-service')
            impositions.add(UserRegistryImposition.of(Registry.of { registry -> registry.add(URL, mockUrl)}))
        }
    }

    void 'ask for a drink'() {
        when:
        def response = app.httpClient.post('bartender/ask')

        then:
        response.statusCode == 200
        response.body.text == 'Hi-ho, mrhaki, ye like to drink some spiced rum!'
    }
    
    void 'tell story'() {
        when:
        def response = app.httpClient.get('bartender/story')

        then:
        response.statusCode == 200
        response.body.text == 'Ay, matey, mrhaki, walk the plank!'
    }
}

Spring Cloud Contract gives us a stub server that is compliant with the contract and even gives back responses based on matched requests from the contracts.

Written with Ratpack 1.4.5 and Spring Cloud Contract 1.0.3.RELEASE.

Ratpacked: Easy URI Creation With HttpUrlBUillder

When we need to create a URI object in Ratpack we can use the HttpUrlBuilder class. We use several methods to build up a complete URI object in an easy way. This is very useful when we for example use Ratpack's HttpClient object and we need to pass an URI to do a request.

In the following example specification we see several usages of the HttpUrlBuilder class:

package mrhaki.sample

import ratpack.http.HttpUrlBuilder
import spock.lang.Specification
import spock.lang.Unroll

class HttpUrlBuilderSpec extends Specification {

    void 'create URI with http protocol'() {
        expect:
        HttpUrlBuilder.http()
                      .host('localhost')
                      .port(5050)
                      .build()
                      .toString() == 'http://localhost:5050'
    }

    @Unroll
    void 'create URI with path'() {
        given:
        final URI server = 'http://server:8080/'.toURI()

        expect:
        HttpUrlBuilder.base(server)
                      .maybePath(path)
                      .build()
                      .toString() == result


        where:
        path   | result
        null   | 'http://server:8080'
        ''     | 'http://server:8080'
        'user' | 'http://server:8080/user'
    }

    void 'create URI with parameters'() {
        expect:
        HttpUrlBuilder.https()
                      .host("localhost")
                      .path('users')
                      .params(page: 2, max: 100)
                      .params('details', 'true')
                      .build()
                      .toString() == 'https://localhost/users?page=2&max=100&details=true'
    }

}

Written with Ratpack 1.4.5.

Ratpacked: Using Spring Cloud Contract To Implement Server

Spring Cloud Contract is a project that allows to write a contract for a service using a Groovy DSL. In the contract we describe the expected requests and responses for the service. From this contract a stub is generated that can be used by a client application to test the code that invokes the service. Spring Cloud Contract also generates tests based on the contract for the service implementation. Let's see how we can use the generated tests for the service implementation for a Ratpack application.

Spring Cloud Contract comes with a Gradle plugin. This plugin adds the task generateContractTests that creates tests based on the contract we write. There are also tasks to create the stub for a client application, but here we focus on the server implementation. In the following Gradle build file for our Ratpack application we use the Spring Cloud Contract Gradle plugin. We configure the plugin to use Spock as framework for the generated tests.

buildscript {
    ext {
        verifierVersion = '1.0.3.RELEASE'
    }
    repositories {
        jcenter()
    }
    dependencies {
        // We add the Spring Cloud Contract plugin to our build.
        classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:${verifierVersion}"
    }
}

plugins {
    id 'io.ratpack.ratpack-java' version '1.4.5'
    id 'com.github.johnrengelman.shadow' version '1.2.4'
    
    // The Spring Cloud Contract plugin relies on
    // the Spring dependency management plugin to 
    // resolve the dependency versions.
    id 'io.spring.dependency-management' version '1.0.0.RELEASE'
}

apply plugin: 'spring-cloud-contract'

repositories {
    jcenter()
}

dependencyManagement {
    imports {
        mavenBom "org.springframework.cloud:spring-cloud-contract-dependencies:${verifierVersion}"
    }
}

dependencies {
    runtime 'org.slf4j:slf4j-simple:1.7.24'

    testCompile 'org.codehaus.groovy:groovy-all:2.4.9'
    testCompile 'org.spockframework:spock-core:1.0-groovy-2.4'
    testCompile 'org.spockframework:spock-spring:1.0-groovy-2.4'
    testCompile 'org.springframework.cloud:spring-cloud-starter-contract-verifier'
    testCompile 'commons-logging:commons-logging:1.2'
}

mainClassName = 'mrhaki.sample.PirateApp'

assemble.dependsOn shadowJar

/**************************************************************
 * Configure Spring Cloud Contract plugin
 *************************************************************/
contracts {
    // Of course we use Spock for the generated specifications.
    // Default is JUnit.
    targetFramework = 'Spock'

    // With explicit testMode real HTTP requests are sent
    // to the application from the specs.
    // Default is MockMvc for Spring applications.
    testMode = 'Explicit'

    // Base class with setup for starting the Ratpack
    // application for the generated specs.
    baseClassForTests = 'mrhaki.sample.BaseSpec'

    // Package name for generated specifications.
    basePackageForTests = 'mrhaki.sample'
}

It is time to write some contracts. We have a very basic example, because we want to focus on how to use Spring Cloud Contract with Ratpack and we don't want to look into all the nice features of Spring Cloud Contract itself. In the directory src/test/resources/contracts/pirate we add a contract for the endpoint /drink:

// File: src/test/resources/contracts/pirata/drink.groovy
package contracts.pirate

import org.springframework.cloud.contract.spec.Contract

Contract.make {
    request {
        method 'GET'
        urlPath '/drink', {
            queryParameters {
                parameter 'name': $(consumer(regex('[a-zA-z]+')), producer('mrhaki'))
            }
        }
        headers {
            contentType(applicationJson())
        }
    }
    response {
        status 200
        body([response: "Hi-ho, ${value(consumer('mrhaki'), producer(regex('[a-zA-z]+')))}, ye like to drink some spiced rum!"])
        headers {
            contentType(applicationJson())
        }
    }
}

We add a second contract for an endpoint /walk:

// File: src/test/resources/contracts/pirata/walk_the_plank.groovy
package contracts.pirate

import org.springframework.cloud.contract.spec.Contract

Contract.make {
    request {
        method 'POST'
        urlPath '/walk'
        body([name: $(consumer(regex('[a-zA-z]+')), producer('mrhaki'))])
        headers {
            contentType(applicationJson())
        }
    }
    response {
        status 200
        body([response: "Ay, matey, ${value(consumer('mrhaki'), producer(regex('[a-zA-z]+')))}, walk the plank!"])
        headers {
            contentType(applicationJson())
        }
    }
}

The last step for generating the Spock specifications based on these contracts is to define a base class for the tests. Inside the base class we use Ratpack's support for functional testing. We define our application with MainClassApplicationUnderTest and use the getAddress method to start the application and to get the port that is used for the application. The generated specifications rely on RestAssured to invoke the HTTP endpoints, so we assign the port to RestAssured:

// File: src/test/groovy/mrhaki/sample/BaseSpec.groovy
package mrhaki.sample

import com.jayway.restassured.RestAssured
import ratpack.test.MainClassApplicationUnderTest
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

abstract class BaseSpec extends Specification {
    
    @Shared
    @AutoCleanup
    def app = new MainClassApplicationUnderTest(PirateApp)
    
    def setupSpec() {
        final URI address = app.address
        RestAssured.port = address.port
    }
}

We can write the implementation for the PirateApp application and use Gradle's check tasks to let Spring Cloud Contract generate the specification and run the specifications. The specification that is generated can be found in build/generated-test-sources and looks like this:

// File: build/generated-test-sources/contracts/mrhaki/sample/PirateSpec.groovy
package mrhaki.sample

import com.jayway.jsonpath.DocumentContext
import com.jayway.jsonpath.JsonPath

import static com.jayway.restassured.RestAssured.given
import static com.toomuchcoding.jsonassert.JsonAssertion.assertThatJson

class PirateSpec extends BaseSpec {

    def validate_drink() throws Exception {
        given:
        def request = given()
                .header("Content-Type", "application/json")

        when:
        def response = given().spec(request)
                              .queryParam("name", "mrhaki")
                              .get("/drink")

        then:
        response.statusCode == 200
        response.header('Content-Type') ==~ java.util.regex.Pattern.compile('application/json.*')
        and:
        DocumentContext parsedJson = JsonPath.parse(response.body.asString())
        assertThatJson(parsedJson).field("response").matches(
                "Hi-ho, [a-zA-z]+, ye like to drink some spiced rum!")
    }

    def validate_walk_the_plank() throws Exception {
        given:
        def request = given()
                .header("Content-Type", "application/json")
                .body('''{"name":"mrhaki"}''')

        when:
        def response = given().spec(request)
                              .post("/walk")

        then:
        response.statusCode == 200
        response.header('Content-Type') ==~ java.util.regex.Pattern.compile('application/json.*')
        and:
        DocumentContext parsedJson = JsonPath.parse(response.body.asString())
        assertThatJson(parsedJson).field("response").matches("Ay, matey, [a-zA-z]+, walk the plank!")
    }

}

If we run Gradle's check task we can see the Spring Cloud Contract plugin tasks are executed as well:

$ gradle check
:copyContracts
:generateContractTests
:compileJava
:compileGroovy NO-SOURCE
:processResources NO-SOURCE
:classes
:compileTestJava NO-SOURCE
:compileTestGroovy
:processTestResources
:testClasses
:test
:check

BUILD SUCCESSFUL

Total time: 5.749 secs

The code for the complete application is on Github.

Written with Ratpack 1.4.5 and Spring Cloud Contract 1.0.3.RELEASE.

November 14, 2016

Ratpacked Notebook Is Updated

I've written a couple of new blog posts about Ratpack the last couple of months, so it was time to also update the Ratpacked Notebook. If you've downloaded the book before, you can download the latest version for free. The following subjects have been added to the new version:

  • Using Spring As Component Registry
  • Using Multiple DataSources
  • Revisited Using Multiple DataSources
  • Include Files In The Ratpack Groovy DSL
  • Stub External HTTP Service
  • Use TestHttpClient For External HTTP Services
  • Handling Exceptions When Reading Configuration Sources
  • Using Groovy Configuration Scripts As Configuration Source
  • Create a Partial Response

October 31, 2016

Ratpacked: Creating Pairs From Promises

The Pair class in Ratpack is an easy way to create a growing data structure, passed on via Promise methods. A Pair object has a left and right part containing data. These parts can even be other Pair objects. Since Ratpack 1.4.0 the Promise class has methods to set the right or left part of a Pair: left, flatLeft, right and flatRight. The result of these methods is a Promise<Pair> object. The input can be Promise type or a Function that can use a previous Promise.

In the following example specification we use the different new methods to create a Pair. We also create a simple Ratpack server with a asynchronous HTTP client implementation to simulate remote calls returning a Promise:

package mrhaki

import ratpack.exec.Promise
import ratpack.func.Pair
import ratpack.groovy.test.embed.GroovyEmbeddedApp
import ratpack.http.HttpUrlBuilder
import ratpack.http.client.HttpClient
import ratpack.test.embed.EmbeddedApp
import ratpack.test.exec.ExecHarness
import spock.lang.AutoCleanup
import spock.lang.Shared
import spock.lang.Specification

class PromisePairSpec extends Specification {

    /**
     * Simple server to serve /{value} and /{value}/size
     * GET requests.
     */
    @Shared
    @AutoCleanup
    private EmbeddedApp serverApi = GroovyEmbeddedApp.of({ 
        handlers {
            get(':value') { render pathTokens.value }
            get(':value/size') { render String.valueOf(pathTokens.value.size()) }
        }
    })

    /**
     * Asynchronous HTTP client. 
     */
    @AutoCleanup
    private HttpClient api = HttpClient.of { client ->
        client.poolSize 1
    }

    def "set right side of Pair with result of function using initial Promise value"() {
        expect:
        ExecHarness.yieldSingle {
            Promise.value('Ratpack')
                    // Use Promise 'Ratpack' in right method as argument.
                   .right { s -> s.size() }
        }.value == Pair.of('Ratpack', 7)
    }

    def "set right side of Pair with result of other Promise"() {
        expect:
        ExecHarness.yieldSingle {
            Promise.value('Ratpack is')
                   // Use Promise value 
                   .right(getApiText('cool'))
        }.value == Pair.of('Ratpack is', 'cool')
    }
    
    def "set right side of Pair with result Promise of function using initial Promise value"() {
        expect:
        ExecHarness.yieldSingle {
            Promise.value('Ratpack')
                   // Use Promise 'Ratpack' in flatRight method as argument.
                   .flatRight { s -> getApiText("${s}/size") }
        }.value == Pair.of('Ratpack', '7')
    }

    def "set right side of Pair with result Promise of function using initial Promise value using flatMap"() {
        expect:
        ExecHarness.yieldSingle {
            Promise.value('Ratpack')
                    // Use Promise 'Ratpack' in flatMap method as argument.
                    // This is the way to set the Pair values 
                    // before the flatRight method was added
                   .flatMap { s -> getApiText("${s}/size").map { content -> Pair.of(s, content) } }
        }.value == Pair.of('Ratpack', '7')
    }

    def "set left side of Pair with result of function using initial Promise value"() {
        expect:
        ExecHarness.yieldSingle {
            Promise.value('Ratpack')
                    // Use Promise 'Ratpack' in left method as argument.
                   .left { s -> s.size() }
        }.value == Pair.of(7, 'Ratpack')
    }

    def "set left side of Pair with result of other Promise"() {
        expect:
        ExecHarness.yieldSingle {
            Promise.value('cool')
                   // Get Promise value without using Promise 'cool' value.
                   .left(getApiText('Ratpack is'))
        }.value == Pair.of('Ratpack is', 'cool')
    }

    def "set left side of Pair with result Promise of function using initial Promise value"() {
        expect:
        ExecHarness.yieldSingle {
            Promise.value('Ratpack')
                    // Use Promise 'Ratpack' in flatLeft method as argument.
                   .flatLeft { s -> getApiText("${s}/size") }
        }.value == Pair.of('7', 'Ratpack')
    }
    
    private Promise<String> getApiText(final String path) {
        api.get(createRequest(path))
           .map { response -> response.body.text }
    }
    
    private URI createRequest(final String path) {
        HttpUrlBuilder.base(serverApi.address).path(path).build()
    }
    
}

Written with Ratpack 1.4.3.

July 6, 2016

Ratpacked: Create a Partial Response

Suppose we want to support partial JSON responses in our Ratpack application. The user must send a request parameter with a list of fields that need to be part of the response. In our code we must use the value of the request parameter and output only the given properties of an object. We implement this logic using a custom renderer in Ratpack. Inside the renderer we can get access to the request parameters of the original request.

In our example Ratpack application we have a Course class, which is a simple class withs some properties:

// File: src/main/groovy/mrhaki/ratpack/Course.groovy
package mrhaki.ratpack

import groovy.transform.Immutable

@Immutable
class Course {
    String name
    String teacher
    Integer maxOccupation
}

Next we create a custom renderer for our Course class. We extend the RendererSupport class and override the render method:

// File: src/main/groovy/mrhaki/ratpack/CourseRenderer.groovy
package mrhaki.ratpack

import ratpack.handling.Context
import ratpack.render.RendererSupport

import static ratpack.jackson.Jackson.json

class CourseRenderer extends RendererSupport<Course> {
    
    @Override
    void render(final Context context, final Course course) throws Exception {
        // Get request parameter fields with a comma separated list
        // of field names to include in the output.
        final String paramFields = context.request.queryParams.get('fields')
        
        if (paramFields) {
            // Transform comma separated property names to a Set.
            final Set<String> coursePropertyNames = 
                    paramFields.tokenize(',').toSet()
            
            // Create Map with only Course properties that need to
            // be included.
            final Map partialCourse = 
                    filterProperties(course, coursePropertyNames)
            
            // Render Map.
            context.render(json(partialCourse))
        } else {
            // No fields request parameter so we can return
            // the original Course object.
            context.render(json(course))
        }
    }

    /**
     * Find all properties in the object that are in the collection
     * of property names.
     * 
     * @param object Object with properties to filter
     * @param propertyNames Names of properties to find
     * @return Map with properties
     */
    private Map filterProperties(
            final Object object, 
            final Set<String> propertyNames) {

        object.properties.findAll { property -> 
            property.key in propertyNames 
        }
    }
}

Finally we need to add the CourseRenderer to the Ratpack registry. Ratpack will find the renderer when we want to render a Course object. This happens automatically, we don't have to do anything ourselves. The following Ratpack application configuration adds our CourseRenderer with the bind method. We also add a endpoint to show the contents of a sample Course object.

// File: src/ratpack/ratpack.groovy
import mrhaki.ratpack.Course
import mrhaki.ratpack.CourseRenderer
import ratpack.registry.Registry

import static ratpack.groovy.Groovy.ratpack

ratpack {
    bindings {
        // Add to registry, so Ratpack can use
        // it to render a Course object.
        bind CourseRenderer
    }
    handlers {
        all {
            final Course course = 
                    new Course(
                            name: 'Ratpack rules 101',
                            teacher: 'mrhaki',
                            maxOccupation: 450)
            next(Registry.single(course))
        }
        get('course') { Course course ->
            render(course)
        }
    }
}

Let's try several requests using the fields request parameter and without the fields request parameter using HTTPie as client:

$ http -b http://localhost:5050/course
{
    "maxOccupation": 450,
    "name": "Ratpack rules 101",
    "teacher": "mrhaki"
}

$ http -b http://localhost:5050/course fields==name,teacher
{
    "name": "Ratpack rules 101",
    "teacher": "mrhaki"
}

Written with Ratpack 1.3.3.

June 27, 2016

Ratpacked: Using Groovy Configuration Scripts As Configuration Source

Ratpack has a lot of options to add configuration data to our application. We can use for example YAML and JSON files, properties, environment variables and Java system properties. Groovy has the ConfigSlurper class to parse Groovy script with configuration data. It even supports an environments block to set configuration value for a specific environment. If we want to support Groovy scripts as configuration definition we write a class that implements the ratpack.config.ConfigSource interface.

We create a new class ConfigSlurperConfigSource and implement the ConfigSource interface. We must implement the loadConfigData method in which we read the Groovy configuration and transform it to a ObjectNode so Ratpack can use it:

// File: src/main/groovy/mrhaki/ratpack/config/ConfigSlurperConfigSource.groovy
package mrhaki.ratpack.config

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.node.ArrayNode
import com.fasterxml.jackson.databind.node.ObjectNode
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import ratpack.config.ConfigSource
import ratpack.file.FileSystemBinding

import java.nio.file.Path

@CompileStatic
class ConfigSlurperConfigSource implements ConfigSource {
    
    private final String configScript
    
    private final URL scriptUrl
    
    private final String environment
    
    ConfigSlurperConfigSource(final String configScript) {
        this(configScript, '')
    }

    ConfigSlurperConfigSource(final String configScript, final String environment) {
        this.configScript = configScript
        this.environment = environment
    }

    ConfigSlurperConfigSource(final Path configPath) {
        this(configPath, '')
    }

    ConfigSlurperConfigSource(final Path configPath, final String environment) {
        this(configPath.toUri(), environment)
    }

    ConfigSlurperConfigSource(final URI configUri) {
        this(configUri, '')
    }

    ConfigSlurperConfigSource(final URI configUri, final String environment) {
        this(configUri.toURL(), environment)
    }

    ConfigSlurperConfigSource(final URL configUrl) {
        this(configUrl, '')
    }

    ConfigSlurperConfigSource(final URL configUrl, final String environment) {
        this.scriptUrl = configUrl
        this.environment = environment
    }

    @Override
    ObjectNode loadConfigData(
            final ObjectMapper objectMapper, 
            final FileSystemBinding fileSystemBinding) throws Exception {

        // Create ConfigSlurper for given environment.
        final ConfigSlurper configSlurper = new ConfigSlurper(environment)

        // Read configuration.
        final ConfigObject configObject = 
                configScript ? 
                        configSlurper.parse(configScript) : 
                        configSlurper.parse(scriptUrl)
        
        // Transform configuration to node tree
        final ObjectNode rootNode = objectMapper.createObjectNode()
        populate(rootNode, configObject)
        return rootNode
    }

    @CompileDynamic
    private populate(final ObjectNode node, final ConfigObject config) {
        // Loop through configuration.
        // ConfigObject also implements Map interface,
        // so we can loop through key/value pairs.
        config.each { key, value ->
            // Value is another configuration,
            // this means the nested configuration
            // block.
            if (value instanceof Map) {
                populate(node.putObject(key), value)
            } else {
                // If value is a List we convert it to
                // an array node.
                if (value instanceof List) {
                    final ArrayNode listNode = node.putArray(key)
                    value.each { listValue ->
                        listNode.add(listValue)
                    }
                } else {
                    // Put key/value pair in node.
                    node.put(key, value)
                }
            }
        }
    }
}

We have several options to pass the Groovy configuration to the ConfigSlurperConfigSource class. We can use a String, URI, URL or Path reference. Let's create a file with some configuration data.

// File: src/ratpack/application.groovy
app {
    serverPort = 9000
}

environments {
    development {
        app {
            serverName = 'local'
        }
    }
    production {
        app {
            serverName = 'cloud'
            serverPort = 80
        }
    }
}

Next we create a Ratpack application using the Groovy DSL. In the serverConfig section we use our new ConfigSlurperConfigSource:

// File: src/ratpack/ratpack.groovy
import com.google.common.io.Resources
import com.mrhaki.config.ConfigSlurperConfigSource

import static groovy.json.JsonOutput.prettyPrint
import static groovy.json.JsonOutput.toJson
import static ratpack.groovy.Groovy.ratpack

//final Logger log = LoggerFactory.getLogger('ratpack')

ratpack {

    serverConfig {
        // Use Groovy configuration.
        add new ConfigSlurperConfigSource('''\
            app {
                message = 'Ratpack swings!'
            }''')

        // Use external Groovy configuration script file.
        add new ConfigSlurperConfigSource(
                Resources.getResource('application.groovy'), 'development')

        require '/app', SimpleConfig
    }

    handlers {
        get('configprops') { SimpleConfig config ->
            render(prettyPrint(toJson(config)))
        }
    }

}

// Simple configuration.
class SimpleConfig {
    String message
    String serverName
    Integer serverPort
}

Let's check the output of the configprops endpoint:

$ http -b localhost:5050/configprops
{
    "message": "Ratpack swings!",
    "serverName": "local",
    "serverPort": 9000
}

Now we set the environment to production in our Ratpack application:

// File: src/ratpack/ratpack.groovy
...

ratpack {

    serverConfig {
        ...

        // Use external Groovy configuration script file.
        add new ConfigSlurperConfigSource(
                Resources.getResource('application.groovy'), 'production')

       ...
    }

    ...
}

If we check configprops again we see different configuration values:

$ http -b localhost:5050/configprops
{
    "message": "Ratpack swings!",
    "serverName": "cloud",
    "serverPort": 80
}

Written with Ratpack 1.3.3.

June 24, 2016

Ratpacked: Handling Exceptions When Reading Configuration Sources

To define configuration sources for our Ratpack application we have several options. We can set default properties, look at environment variables or Java system properties, load JSON or YAML formatted configuration files or implement our own configuration source. When something goes wrong using one of these methods we want to be able to handle that situation. For example if an optional configuration file is not found, we want to inform the user, but the application must still start. The default exception handling will throw the exception and the application is stopped. We want to customise this so we have more flexibility on how to handle exceptions.

We provide the configuration source in the serverConfig configuration block of our Ratpack application. We must add the onError method and provide an error handler implementation before we load any configuration source. This error handler will be passed to each configuration source and is execute when an exception occurs when the configuration source is invoked. The error handler implements the Action interface with the type Throwable. In our implementation we can for example check for the type of Throwable and show a correct status message to the user.

In the following example application we rely on external configuration source files that are optional. If the file is present it must be loaded, otherwise a message must be shown to indicate the file is missing, but the application still starts:

// File: src/ratpack/ratpack.groovy
import org.slf4j.Logger
import org.slf4j.LoggerFactory

import java.nio.file.NoSuchFileException
import java.nio.file.Paths

import static ratpack.groovy.Groovy.ratpack

final Logger log = LoggerFactory.getLogger('ratpack.server')

ratpack {
    serverConfig {
        // Use custom error handler, when
        // exceptions happen during loading
        // of configuration sources.
        onError { throwable ->
            if (throwable in NoSuchFileException) {
                final String file = throwable.file
                log.info "Cannot load optional configuration file '{}'", file
            } else {
                throw throwable
            }
        }
        
        yaml('application.yml')

        // Optional configuration files
        // to override values in 
        // 'application.yml'. This could
        // potentially give an exception if
        // the files don't exist.
        yaml(Paths.get('conf/application.yml'))
        json(Paths.get('conf/application.json'))
        
        args(args)
        sysProps()
        env()
        
        ...
    }

    ...
}

Next we start the application with the absence of the optional configuration files conf/application.yml and conf/application.json:

$ gradle run
...
:run

12:28:38.887 [main]            INFO  ratpack.server.RatpackServer - Starting server...
12:28:39.871 [main]            INFO  ratpack.server - Cannot load optional configuration file 'conf/application.yml'
12:28:39.873 [main]            INFO  ratpack.server - Cannot load optional configuration file 'conf/application.json'
12:28:40.006 [main]            INFO  ratpack.server.RatpackServer - Building registry...
12:28:40.494 [main]            INFO  ratpack.server.RatpackServer - Ratpack started (development) for http://localhost:5050

Notice that application is started and in the logging we have nice status messages that tell us the files are not found.

Written with Ratpack 1.3.3.

June 15, 2016

Ratpacked: Use TestHttpClient For External HTTP Services

Ratpack has a very useful class: TestHttpClient. This is a blocking HTTP client that we normally use for testing our Ratpack applications. For example we use MainClassApplicationUnderTest or GroovyRatpackMainApplicationUnderTest in a test and invoke the getHttpClient method to get an instance of TestHttpClient. The class has a lot of useful methods to make HTTP requests with a nice DSL. TestHttpClient is also very useful as a standalone HTTP client in other applications.

Suppose we have a piece of code that needs to access MapQuest Open Platform Web Services to get location details for a given combination of longitude and latitude values. In the constructor we create an instance of the interface ApplicationUnderTest. We then can use the getHttpClient method of ApplicationUnderTest to get a TestHttpClient instance:

// File: src/main/groovy/mrhaki/geocode/GeocodeService.groovy
package mrhaki.geocode

import groovy.json.JsonSlurper
import ratpack.http.client.ReceivedResponse
import ratpack.test.ApplicationUnderTest
import ratpack.test.http.TestHttpClient

class GeocodeService {

    private final ApplicationUnderTest mapQuestApi
    private final GeocodeConfig config

    GeocodeService(final GeocodeConfig config) {
        this.config = config
        
        // Create ApplicationUnderTest using a Closure,
        // which returns the URI of our external HTTP service.
        // The ApplicationUnderTest interface only 
        // has one method (URI getAddress()), 
        // so we can use a Closure to implement the interface.
        mapQuestApi = { config.uri.toURI() }
    }

    Location getLocation(final Double latitude, final Double longitude) {
        // Create a blocking HttpClient with the base
        // URI from ApplicationUnderTest.
        final TestHttpClient httpClient = mapQuestApi.httpClient

        // Request location details for given latitude and longitude
        // and set the application key.
        httpClient.params { paramBuilder ->
            paramBuilder.put 'key', config.apiKey
            paramBuilder.put 'location', [latitude, longitude].join(',')
        }
        
        // Use get method to get a response from the HTTP service.
        final ReceivedResponse response = httpClient.get('geocoding/v1/reverse') 
        
        // Transform JSON result and
        // find location specific details in the response.
        final jsonResponse = new JsonSlurper().parseText(response.body.text)
        final location = jsonResponse.results[0].locations[0]
        
        // Create new Location object.
        new Location(street: location.street, city: location.adminArea5)
    }
    
}

The host name and key we need to make a request are set via the GeocodeConfig class:

// File: src/main/groovy/mrhaki/geocode/GeocodeConfig.groovy
package mrhaki.geocode

class GeocodeConfig {
    String apiKey
    String uri
}

And finally a simple POGO to store the location details:

// File: src/main/groovy/mrhaki/geocode/Location.groovy
package mrhaki.geocode

import groovy.transform.Immutable

@Immutable
class Location {
    String street
    String city
}

In our project we only have to add a dependency on io.ratpack:ratpack-test:

// File: build.gradle
...
dependencies {
    ...
    compile group: 'io.ratpack', name: 'ratpack-test', version: '1.3.3'
    ...
}
...

Written with Ratpack 1.3.3.

Ratpacked: Stub External HTTP Service

Suppose we have a piece of code that uses an external HTTP service. If we write a test for this code we can invoke the real HTTP service each time we execute the tests. But it might be there is a request limit for the service or the service is not always available when we run the test. With Ratpack it is very, very easy to write a HTTP service that mimics the API of the external HTTP service. The Ratpack server is started locally in the context of the test and we can write extensive tests for our code that uses the HTTP service. We achieve this using the Ratpack EmbeddedApp or GroovyEmbeddedApp class. With very little code we configure a server that can be started and respond to HTTP requests.

In our example project we have a class GeocodeService that uses the external service MapQuest Open Platform Web Services. We use the HTTP Requests library to make a HTTP request and transform the response to an object:

// File: src/main/groovy/mrhaki/geocode/GeocodeService.groovy
package mrhaki.geocode

import com.budjb.httprequests.HttpClient
import com.budjb.httprequests.HttpResponse

class GeocodeService {

    private final HttpClient httpClient
    private final GeocodeConfig config

    GeocodeService(
            final HttpClient httpClient,
            final GeocodeConfig config) {

        this.httpClient = httpClient
        this.config = config
    }

    Location getLocation(final Double latitude, final Double longitude) {
        // Request location details for given latitude and longitude
        // using a external HTTP service.
        final HttpResponse response =
                httpClient.get {
                    uri = "${config.uri}geocoding/v1/reverse".toURI()

                    addQueryParameter 'key', config.apiKey
                    addQueryParameter 'location', [latitude, longitude].join(',')
                }
        
        // Transform JSON result to Map.
        final Map responseMap = response.getEntity(Map)
        
        // Find location specific details in the response.
        final Map location = responseMap.results[0].locations[0]
        
        // Create new Location object.
        new Location(street: location.street, city: location.adminArea5)
    }
}

The host name and key we need to make a request are set via the GeocodeConfig class:

// File: src/main/groovy/mrhaki/geocode/GeocodeConfig.groovy
package mrhaki.geocode

class GeocodeConfig {
    String apiKey
    String uri
}

And finally a simple POGO to store the location details:

// File: src/main/groovy/mrhaki/geocode/Location.groovy
package mrhaki.geocode

import groovy.transform.Immutable

@Immutable
class Location {
    String street
    String city
}

To access the real MapQuest API service we would set the host and key in the GeocodeConfig object and we get results from the web service. Now we want to write a Spock specification and instead of accessing the real API, we implement the MapQuest API with Ratpack.

// File: src/test/groovy/mrhaki/geocode/GeocodeServiceSpec.groovy
package mrhaki.geocode

import com.budjb.httprequests.HttpClient
import com.budjb.httprequests.HttpClientFactory
import com.budjb.httprequests.jersey2.JerseyHttpClientFactory
import ratpack.groovy.test.embed.GroovyEmbeddedApp
import ratpack.test.CloseableApplicationUnderTest
import spock.lang.AutoCleanup
import spock.lang.Specification
import spock.lang.Subject

import static ratpack.jackson.Jackson.json

class GeocodeServiceSpec extends Specification {

    @AutoCleanup
    private CloseableApplicationUnderTest mapQuestApi = mapQuestApiServer()

    @Subject
    private GeocodeService geocodeService

    def setup() {
        final HttpClientFactory httpClientFactory = new JerseyHttpClientFactory()
        final HttpClient httpClient = httpClientFactory.createHttpClient()

        // Get address and port for Ratpack
        // MapQuest API server.
        final String serverUri = mapQuestApi.address.toString()

        final GeocodeConfig config =
                new GeocodeConfig(
                        apiKey: 'secretApiKey',
                        uri: serverUri)

        geocodeService = new GeocodeService(httpClient, config)
    }

    def "get location from given latitude and longitude"() {
        when:
        final Location location = geocodeService.getLocation(52.0298141, 5.096626)

        then:
        with(location) {
            street == 'Marconibaan'
            city == 'Nieuwegein'
        }
    }

    private GroovyEmbeddedApp mapQuestApiServer() {
        // Create a new Ratpack server, with
        // a single handler to mimic MapQuest API.
        GroovyEmbeddedApp.fromHandlers {
            get('geocoding/v1/reverse') {
                // Extra check to see if required parameters
                // are set. This is optional, we could also
                // ignore them in this stub implementation.
                if (!request.queryParams.key) {
                    response.status = 500
                    response.send('Query parameter "key" not set')
                    return
                }
                if (!request.queryParams.location) {
                    response.status = 500
                    response.send('Query parameter "location" not set')
                    return
                }

                // Create a response, like the real API would do.
                // In this case a fixed value, but we could do 
                // anything here, for example different responses, based
                // on the location request parameter. 
                final Map response = 
                    [results: [
                        [locations: [
                            [street: 'Marconibaan', adminArea5: 'Nieuwegein']]]]]
                render(json(response))
            }
        }
    }
}

To run our test we only have to add Ratpack as a dependency to our project. The following example Gradle build file is necessary for this project:

// File: build.gradle
apply plugin: 'groovy'

repositories {
    jcenter()
}

dependencies {
    compile group: 'org.codehaus.groovy', name: 'groovy-all', version: '2.4.7'
    
    // HttpRequests library to access HTTP services.
    compile group: 'com.budjb', name: 'http-requests-jersey2', version: '1.0.1'

    testCompile group: 'org.spockframework', name: 'spock-core', version: '1.0-groovy-2.4'
    
    // Include this Ratpack dependency for the GroovyEmbeddedApp class,
    // we need in the specification.
    testCompile group: 'io.ratpack', name: 'ratpack-groovy-test', version: '1.3.3'
}

Ratpack makes it so easy to create a new HTTP service and in this case use it in a test.

Written with Ratpack 1.3.3.