{% extends "../helloWorld.html" %} {% block demoSectionClasses %}demo_hello_world{% endblock %} {% block meta_title %}Demo - Hello World - Supercalifragilisticexpialidocious{% endblock %} {% block meta_description %}"Hello World!" demo and tutorial using Spincast - Supercalifragilisticexpialidocious version{% endblock %} {% set demoId = "super" %} {% block scripts %} {% endblock %} {% block demoBody %}

3. Supercalifragilisticexpialidocious version

In this tutorial, we will develop a "Hello World!" application using more advanced Spincast functionalities. We will :

As you can see in the Quick version tutorial, it is really easy to start a Spincast application if you simply need the default functionalities. But you can go far beyond that...

Spincast is made from the ground up to be extensible, flexible. All the parts can be swapped or be extended since dependency injection is used everywhere and there are no private methods.

Spincast artifact

First, let's add the org.spincast:spincast-default Maven artifact to our pom.xml (or build.gradle) so we start with the default plugins. It is interesting to know that to have total control, we could also start with the org.spincast:spincast-core artifact instead and pick, one by one, which plugins to use. But, most of the time, you'll start with the default artifact :

<dependency>
    <groupId>org.spincast</groupId>
    <artifactId>spincast-default</artifactId>
    <version>{{spincast.spincastCurrrentVersion}}</version>
</dependency>

Adding a plugin

In this application, we're going to use a plugin which is not installed by default : Spincast HTTP Client. This plugin provides an easy way to make HTTP requests.

Adding a plugin is, in general, a two steps process.

1. First, we add its Maven artifact to our pom.xml (or build.gradle) :

<dependency>
    <groupId>org.spincast</groupId>
    <artifactId>spincast-plugins-http-client</artifactId>
    <version>{{spincast.spincastCurrrentVersion}}</version>
</dependency>

2. Then, since most plugins need to add or modify some bindings in the Guice context of an application, we have to register them. This is done using the "plugin(...)" method of the Bootstrapper :

public static void main(String[] args) {

    Spincast.configure()
            .plugin(new SpincastHttpClientPlugin())
            //...
            .init();
   //...
}

We'll come back to this bootstrapping part as it is not complete like this. For now, let's simply notice how the plugin is registered.

Custom Request Context type

Creating a custom Request Context type is optional but suggested. You can very well develop a complete and production ready Spincast application without one... But it is a powerful feature as it allows plugins to add functionalities to the Request Context objects that are passed to your Route Handlers when request are received. You can learn more about this in the Request Context section.

Note that if you use the Quick Start application as a template for your application, a custom Request Context type is already provided : you simply have to add add-ons to it, when required.

In this application, we will add a "httpClient()" add-on to our custom Request Context type, so we can easily make HTTP requests from our Route Handlers.

Here's the interface we are going to use for our custom Request Context :

public interface AppRequestContext extends RequestContext<AppRequestContext> {

    /**
     * Add-on to access the HttpClient factory
     */
    public HttpClient httpClient();
    
    //... other add-ons
}

And an implementation for it :

public class AppRequestContextDefault extends RequestContextBase<AppRequestContext>
                                      implements AppRequestContext {

    private final HttpClient httpClient;
    
    @AssistedInject
    public AppRequestContextDefault(@Assisted Object exchange,
                                    RequestContextBaseDeps<AppRequestContext> requestContextBaseDeps,
                                    HttpClient httpClient) {
        super(exchange, requestContextBaseDeps);
        this.httpClient = httpClient;
    }

    @Override
    public HttpClient httpClient() {
        return this.httpClient;
    }
}

Notice that we injected the HttpClient component (9) which is in fact a factory to start new HTTP requests. Our add-on method, "httpClient()" simply returns this factory (16).

You can learn more about the process of extending the Request Context type in the dedicated section of the documentation.

Controller and Route Handlers

Let's now create a controller, some Route Handlers, and use the new add-on we just added :

public class AppController {

    /**
     * Simple "Hello World" response on the "/" Route.
     */
    public void indexPage(AppRequestContext context) {
        context.response().sendPlainText("Hello World!");
    }

    /**
     * Route Handler for the "/github-source/${username}" Route.
     * 
     * We retrieve the HTML source of the GitHub page associated
     * with the specified username, and return it in a Json object.
     */
    public void githubSource(AppRequestContext context) {

        String username = context.request().getPathParam("username");

        String url = "https://github.com/" + username;

        String src = context.httpClient().GET(url).send().getContentAsString();

        JsonObject response = context.json().create();
        response.put("username", username);
        response.put("url", url);
        response.put("source", src);

        context.response().sendJson(response);
    }
}

Explanation :

Route definitions

In this tutorial, we won't define the Routes in the App class, but directly in the controller! Let's do this :

public class AppController {

    public void indexPage(AppRequestContext context) { ... }

    public void githubSource(AppRequestContext context) { ... }

    /**
     * Init method : we inject the Router and then add some Routes to it.
     */
    @Inject
    protected void init(Router<AppRequestContext, DefaultWebsocketContext> router) {

        router.GET("/").save(this::indexPage);
        router.GET("/github-source/${username}").save(this::githubSource);
    }   
}

As you can see the Router is dynamic, you can inject the Router in any component in order to add Routes to it. Here, our controller simply uses method reference to bind some of its own methods as Route Handlers.

You may also notice that the type of the injected Router is Router<AppRequestContext, DefaultWebsocketContext>, which is kind of ugly. It is so because we use a custom Request Context type, and all components related to routing have to be aware of it. We won't do it in this tutorial, but it's very easy to create a unparameterized version of those routing components so they are prettier and easier to deal with!

Configurations

We are going to change the port the Server will be started on by overriding the default SpincastConfig binding. To do so, we create a custom class that extends the default SpincastConfigDefault implementation :

public class AppConfig extends SpincastConfigDefault {

    // We change the port the Server will be started on
    @Override
    public int getHttpServerPort() {
        return 4242;
    }
}

Application Guice module

Let's now create a Guice module for our application, and bind our custom components to it :

public class AppModule extends SpincastGuiceModuleBase {

    @Override
    protected void configure() {

        bind(AppController.class).asEagerSingleton();

        bind(SpincastConfig.class).to(AppConfig.class).in(Scopes.SINGLETON);

        // ... other bindings
    }
}

Explanation :

The App class

The only missing piece is the App class itself, where the main(...) method and the Bootstrapper are defined :

public class App {

    public static void main(String[] args) {
    
        Spincast.configure()
                .module(new AppModule())
                .plugin(new SpincastHttpClientPlugin())
                .requestContextImplementationClass(AppRequestContextDefault.class)
                .mainArgs(args)
                .init();
    }

    @Inject
    protected void init(Server server) {
        server.start();
    }
}

Explanation :

Try it!

Download this application, and run it by yourself :

The application is then accessible at http://localhost:4242

Don't forget to try the "github-source" Route (you can change the "username" dynamic parameter) : http://localhost:4242/github-source/spincast.

{% endblock %}