Getting Started with Knot.x Stack

This post may be changed. It referes to development version.
Maciej Laskowski
1.3.0 1.4.0 1.5.0 edge

Overview

In this tutorial we will configure Knot.x instance with a simple page that uses an external datasource (Google API Books) to fetch the dynamic data and display it on our page.

What you’re going to learn:

  • How to setup Knot.x instance using Knot.x Stack
  • How to transform a static HTML into the dynamic content and configure Knot.x to use REST services to get data
  • How to use the data from such services to dynamically populate HTML

Setup basic Knot.x instance

Prerequisites You will need the following things to use Knot.x stack:

Download Latest Knot.x Stack release and unzip it.

For the purpose of this tutorial let's call the structure of unzipped stack KNOTX_HOME. KNOTX_HOME which is Knot.x instance with all configuration files and dependencies has the following structure:

├── bin
|   ├── knotx                     // shell script used to run Knot.x instance
│   └── knotx.bat                 // Windows script used to run Knot.x instance                      
├── conf                          // contains application and logger configuration files
│   ├── application.conf          // defines / includes all modules that Knot.x instance is running
│   ├── bootstrap.json            // config retriever options, defines application configuration stores (e.g. points to `application.conf` - the main configuration)
│   ├── openapi.yaml              // Open API 3.0 configuration that is loaded via Knot.x HTTP Server
│   ├── server.conf               // Knot.x HTTP server configuration which is included in `application.conf`
│   ├── routes                    // server routes configurations 
│   │   ├── operations.conf       // defines handlers per Open API operation ids
│   │   └── handlers              // handlers configurations used in `operations.conf`
|   │   │   ├── fragmentsHandler.conf
|   │   │   └── httpRepoConnectorHandler.conf
│   ├── knots                     // Knot modules configurations which are included in `application.conf`
│   │   ├── templateEngineStack.conf
│   │   └── templateEngineKnot.conf
│   └── logback.xml          // logger configuration
├── lib                      // contains instance libraries and dependencies, instance classpath
│   ├── the list of project dependency libraries
│   ├── ...

Now, run

bin/knotx run-knotx

to start the Knot.x instance. You should see that the instance is running with all deployed modules. Following entries should appear in the logs/knotx.log file:

2019-08-05 13:50:26.380 [vert.x-eventloop-thread-0] INFO  i.k.launcher.KnotxStarterVerticle - STARTING Knot.x
2019-08-05 13:50:26.492 [vert.x-eventloop-thread-2] INFO  io.knotx.te.core.TemplateEngineKnot - Starting <TemplateEngineKnot>
2019-08-05 13:50:26.497 [vert.x-eventloop-thread-2] INFO  i.k.te.core.TemplateEngineProvider - Template Engines [handlebars] registered.
2019-08-05 13:50:26.500 [vert.x-eventloop-thread-2] INFO  i.k.t.h.HandlebarsTemplateEngine - <HandlebarsTemplateEngine> instance created
2019-08-05 13:50:26.501 [vert.x-eventloop-thread-1] INFO  io.knotx.server.KnotxServerVerticle - Starting <KnotxServerVerticle>
2019-08-05 13:50:26.504 [vert.x-eventloop-thread-1] INFO  io.knotx.server.KnotxServerVerticle - Open API specification location [/openapi.yaml]
2019-08-05 13:50:26.639 [vert.x-eventloop-thread-1] INFO  i.k.server.GlobalHandlersProvider - Global handler loggerHandler registered
2019-08-05 13:50:26.641 [vert.x-eventloop-thread-1] INFO  io.knotx.server.SecurityProvider - Auth handler factory types registered: 
2019-08-05 13:50:26.645 [vert.x-eventloop-thread-1] INFO  io.knotx.server.RoutesProvider - Routing handler factory names [fragmentsAssemblerHandler,fragmentsHandler,fragmentsProviderHtmlSplitter,singleFragmentSupplier,fsRepoConnectorHandler,httpRepoConnectorHandler,csrfHandler,loggerHandler,bodyHandler,cookieHandler,errorHandler,requestContextHandler,headerHandler,writerHandler] registered.
2019-08-05 13:50:27.346 [vert.x-eventloop-thread-1] INFO  io.knotx.server.RoutesProvider - Initialized all handlers for operation [operation-get]
2019-08-05 13:50:27.362 [vert.x-eventloop-thread-1] INFO  io.knotx.server.KnotxServerVerticle - Routes [[Route[ path:null pattern:null handlers:[io.vertx.ext.web.handler.impl.BodyHandlerImpl@6ab5ad9, io.vertx.reactivex.ext.web.api.contract.openapi3.OpenAPI3RouterFactory$5@ca253d2] failureHandlers:[] order:0 methods:[]]@1683416257, Route[ path:/content/ pattern:null handlers:[io.vertx.ext.web.api.contract.openapi3.impl.OpenAPI3RequestValidationHandlerImpl@28f91299, io.vertx.reactivex.ext.web.api.contract.openapi3.OpenAPI3RouterFactory$8@7458e8cd, io.vertx.reactivex.ext.web.api.contract.openapi3.OpenAPI3RouterFactory$8@6a7a1957, io.vertx.reactivex.ext.web.api.contract.openapi3.OpenAPI3RouterFactory$8@39d98835, io.vertx.reactivex.ext.web.api.contract.openapi3.OpenAPI3RouterFactory$8@59d4284d, io.vertx.reactivex.ext.web.api.contract.openapi3.OpenAPI3RouterFactory$8@6a79ad3a, io.vertx.reactivex.ext.web.api.contract.openapi3.OpenAPI3RouterFactory$8@5073b183, io.vertx.reactivex.ext.web.api.contract.openapi3.OpenAPI3RouterFactory$8@259e1398, io.vertx.reactivex.ext.web.api.contract.openapi3.OpenAPI3RouterFactory$8@5d70d126, io.vertx.reactivex.ext.web.api.contract.openapi3.OpenAPI3RouterFactory$8@7ac7a7cf] failureHandlers:[] order:1 methods:[GET]]@930479805]]
2019-08-05 13:50:28.012 [vert.x-eventloop-thread-1] INFO  io.knotx.server.KnotxServerVerticle - Knot.x HTTP Server started. Listening on port 8092
2019-08-05 13:50:28.016 [vert.x-eventloop-thread-0] INFO  i.k.launcher.KnotxStarterVerticle - Instance modules: 
Deployed 1 instance(s) of required templateEngine (java:io.knotx.te.core.TemplateEngineKnot) [313308b7-e9eb-4a32-b26b-331f79ce9e2c]
Deployed 1 instance(s) of required server (java:io.knotx.server.KnotxServerVerticle) [56784ec6-9981-41f2-8ed6-404301cfdc5d]

2019-08-05 13:50:28.017 [vert.x-eventloop-thread-0] INFO  i.k.launcher.KnotxStarterVerticle - Knot.x STARTED successfully

Congratulation! That's it. You have your own basic Knot.x instance running.

Configuration

Lets now configure Knot.x to do the magic for us. We need to do two things:

  • provide the page template, for the tutorial purpose, we will use fsRepoConnectorHandler,
  • provide the datasource, we will use the Google Books API.

All configuration options and default values, such as address fields, for each Knot.x module, are described directly in the configuration files of those modules in conf.

HTML Template

Create a repository/content directory in KNOTX_HOME and put there following page template with Knot.x snippet (<knotx:snippet data-knotx-task="bookslist">...):

books.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Knot.x</title>
  <link href="https://bootswatch.com/4/superhero/bootstrap.min.css" rel="stylesheet"/>
</head>
<body>
<div class="container-fluid">
  <div class="row">
    <div class="col-sm-12">
      <div class="panel panel-default">
        <div class="panel-heading">Books list</div>
        <div class="panel-body">
          This page lists Java related books provided by <a
            href="https://www.googleapis.com/books/v1/volumes?q=java">Googleapis book service</a>.
        </div>
      </div>
    </div>
  </div>
  <div class="row">
   <knotx:snippet data-knotx-task="bookslist">
      {{#each _result.items}}
        <div class="col-sm-3">
        <div class="card">
          <img class="card-img-top"
             src="{{volumeInfo.imageLinks.thumbnail}}"
             alt="{{volumeInfo.title}}">
          <div class="card-block">
            <h4 class="card-title">{{volumeInfo.title}}</h4>
            <p class="card-text">
              {{#each volumeInfo.authors as |author|}}
                {{author}}{{#unless @last}}, {{/unless}}
              {{/each}}<br />
              Published: {{volumeInfo.publishedDate}}
            </p>
          </div>
         </div>
      </div>
      {{/each}}
    </knotx:snippet>

    </div>
</div>
</body>
</html>

Templates repository configuration

We will use fsRepoConnectorHandler, its purposes are purely academical. It is not designed to be used as a production ready solution (you should use httpRepoConnectorHandler there).

Create conf\routes\handlers\fsRepoConnectorHandler.conf file with the following:

# Path to the directory on the local filesystem which will be the root for requested templates
catalogue = "./repository/"

This way we define the repository directory that will be our files content repository.

Edit the conf/routes/operations.conf file and:

  • replace httpRepoConnectorHandler to fsRepoConnectorHandler in operation-get definition
  • change included configuration file into one you have just created routes/handlers/fsRepoConnectorHandler.conf

Your conf/routes/operations.conf should contain:

routingOperations = ${routingOperations} [
  {
    operationId = operation-get
    handlers = ${config.server.handlers.common.request} [
      {
        name = fsRepoConnectorHandler
        config = {include required(classpath("routes/handlers/fsRepoConnectorHandler.conf"))}
      },
      {
        name = htmlFragmentsSupplier
      },
      {
        name = fragmentsHandler
        config = {include required(classpath("routes/handlers/fragmentsHandler.conf"))}
      },
      {
        name = fragmentsAssembler
      }
    ] ${config.server.handlers.common.response}
  }
]
  • Save the changes You may see that the Knot.x instance detected file changes and reloaded its configuration.

Task configuration

As you probably noticed the Knot.x snippet you defined in the books.html template file pointed to the bookslist task. Let's define it.

Edit conf/routes/handlers/fragmentsHandler.conf

  • Add following in tasks section:
  bookslist {
    action = books
    onTransitions {
      _success {
        action = te-hbs   
      }
    }
  }

Your task definition requires two actions: books and te-hbs, let's define them. The actions section should contain:

  books {
    factory = http
    config {
      webClientOptions {
          ssl = true
      }
      endpointOptions {
        path = "/books/v1/volumes?q=java"
        domain = www.googleapis.com
        port = 443
        allowedRequestHeaders = ["Content-Type"]
      }
    }
  }
  te-hbs {
    factory = knot
    config {
      address = knotx.knot.te.handlebars
      deliveryOptions {
        sendTimeout = 1000
      }
    }
  }  

Save the configuration file, Knot.x will reload its modules once again.

The last thing left, open http://localhost:8092/content/books.html - Voilà!

You can find full example implementation here