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:
- JDK 8
- Linux or OSX bash console (for Windows users we recommend using e.g. Ubuntu with Windows Subsystem for Linux).
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
tofsRepoConnectorHandler
inoperation-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