Thursday, February 2, 2017

Getting started with the Kitura Stencil framework

In our last post we showed how to create REST based web services using Swift and IBM’s Kitura framework.  In this post we will show how to serve both static and dynamic web pages with Swift using the Kitura and the Kitura Stencil frameworks.  The Kitura framework, developed by IBM, is a light-weight, high-performance, web framework and server written in the Swift language.  The Kitura stencil framework allows us to separate our view component from the controller code when we are serving web pages.

If you are not familiar with what IBM is doing with server side Swift, I would recommend you visit their web site at https://developer.ibm.com/swift/.  One note, all code for this post was written and tested on an Ubuntu 16.10 laptop.  The code should also work on macOS but you may need to install other dependencies. 

We will start off by creating the project using the Swift Package Manager. To do this we will create a directory named kitura_web_page_sample and then initialize the project with the Swift package manager:

mkdir kitura_web_page_sample
cd kitura_web_page_sample
swift package init

The first thing we need to do is add the Kitura framework as a dependency for our project. We do this by adding the dependency to the Package.swift file.  The Package.swift file should have the following code in it:

import PackageDescription

let package = Package(
    name: "kitura_web_page_sample",
    dependencies: [
        .Package(url: "https://github.com/IBM-Swift/Kitura.git", majorVersion: 1, minor: 4)
    ]
)

Before we show how to use stencils and properly serve both static and dynamic web pages with Kitura, we will start off by showing what NOT to do.  Let’s create the main.swift file under the Sources directory and put the following code in it:

import Kitura

// Create a new router
let router = Router()

router.get("/dontdo") {
    request, response, next in
    response.send("<html><body>")
    response.send("<h1>Don't do this</h1>'")
    response.send("</body></html>")
    next()
}

// Add an HTTP server and connect it to the router
Kitura.addHTTPServer(onPort: 8090, with: router)

// Start the Kitura runloop (this call never returns)
Kitura.run()

The first line imports the Kitura framework so we can use it in our code.  Next, we create an instance of the Router class.  The Router class provides an interface for routing the incoming requests to the correct code.  This class lets us handle all standard REST request types. You can find the reference page for the Router class here:  http://ibm-swift.github.io/Kitura/Classes/Router.html#/s:FC6Kitura6Router3getFtGSqSS_7handlerGSaFzTCS_13RouterRequestCS_14RouterResponseFT_T__T___S0_

We use the get method to set up a router handler that will be invoked when an HTTP GET request comes in with a path pattern that matches the supplied path.  In this example, the path that we are matching against is “/donotdo” because we should not be serving web pages this way.  If a request does come in with the correct path, the code in the closure is called.  In our service, we send back a HTML page that displays the text “Don’t do this”.

We use the addHTTPServer(onPort:with:) to register the router.  The onPort parameter is the port number to bind the server too.  In this example, we will bind to port 8090.  This call only registers the server, it does not start listening until we call the run()  method in the last line.

Now let’s save the file and run Swift build from our project’s root directory.

If you receive an error that the compile could not find curl/curl.h, you will need to install the libcurl4-openssl-dev package (or any of the other three packages that provide the curl.h header file).  To do this run the following command:
sudo apt-get install libcurl4-openssl-dev

If everything compiles correctly we can run the application with the following command from the project’s root directory:

./.build/debug/kitura_web_page_sample

If everything starts up correctly, we can open any web browser and see the page by going to http://localhost:8090/donotdo. 

Now the question may be, why shouldn’t we do this.  It is good practice to always try to separate the view component from the controller.  With the previous code our view (HTML code) is embedded within our controller which means we will have to update the code every time the view changes.  This is definitely not ideal especially since the person that creates the view can be different from the person that writes the controller.

To separate the view from the controller we can use Kitura templates.  The Kitura template engine allows us to render web pages from static templates.  This allows us to update the view by updating the template rather than the controller code.  Let’s see how we can use the Kitura template engine.  The first thing we need to do is to update the dependencies for our project.  The following code shows the new Package.swift file with the updated dependencies.

import PackageDescription

let package = Package(
    name: "kitura_web_page_sample",
    dependencies: [
        .Package(url: "https://github.com/IBM-Swift/Kitura.git", majorVersion: 1, minor: 4),
        .Package(url: "https://github.com/IBM-Swift/Kitura-StencilTemplateEngine.git", majorVersion: 1, minor: 4)
    ]
)

Now let’s create a template.  Templates reside in a directory called Views off of the project’s root directory. The following command will create this directory:

mkdir Views

The stencil files use a file extension of .stencil.  Let’s create a stencil file named home.stencil and put the following code in it.
<html>
  <body>
    <h2>Hello Turtles</h1>
    <p>
  </body>
</html>

Notices that this stencil file contains standard HTML at this point.  To use this template, we need to import the Kitura Stencil framework into our code.  Add the following import to the main.swift file:

Import KituraStencil

Now we need to set the stencil engine right after we create the router.  The following code shows how to do this:

// Create a new router
let router = Router()

//Setup Stencil
router.setDefault(templateEngine: StencilTemplateEngine())



Finally we will create a router handler that will use the stencil.  Put the following code in the main.swift file right below the other router handler:

router.get("/home") {
    request, response, next in
    defer { next() }
    try response.render("home", context: [:])
}

Notice the try response.render(“home”, context: [:]) line in the router handler.  This line will look in the Views directory for a file named home.stencil and render the page from the template.  The context parameter is used to pass data to the template.  We will see how to use the context parameter a little later in this post but before we do that let’s see how we can use a template within another template.

There are times when we would like to use a standard header or footer for all web pages.  We really do not want to put this code in each template because then when someone wants to changes the header or footer we would have to make the changes to each template.  With the Kitura stencil framework we can embed one stencil within another.  Let’s see how this works by creating a simple header named header.stencil in the Views directory and put this single line of code in it:

<h1>Mega Turtles</h1>

We can now update the home.stencil file so it contains the following code:

<html>
  <body>
    {% include "header.stencil" %}
    <h2>Hello Turtles</h1>
    <p>
  </body>
</html>

Notice that we added the {% include “header.stencil” %} line to the file.  This line will search the Views directory for a file named header.stencil and embed the code from that file into this page.  If we build and run the application we will see that the Mega Turtles header is now at the top of the page.

When we refer to these files we refer to them as templates however you may be wondering why we use the .stencil extension.  This is because we are using the Stenciltemplate engine.  This stencil engine allows us to substitute template variables with actual values at runtime.  This allows us to create dynamic web pages.  We saw a basic example of this in the previous post where we added the header template to the page.

Let’s see how we can use the template engine to create dynamic pages.  The first thing we need to do is to create a new template.  Let’s create a template named list.stencil in the Views directory and put the following code in it.

<html>
  <body>
    {% include "header.stencil" %}
    There are {{ turtles.count }} turtles.
  </body>
</html>

Notice the new line in this file.  Before we explain this line, let’s look at the router handler that will use this template.

    router.get("/list") {        request, response, next in        defer { next() }        var context = [String: Any]()        context["turtles"] = turtleNames        try response.render("list", context: context)    }

Within this router handler, we define an array named turtleNames with four elements.  We also create a dictionary object named context.  This dictionary will contain the data for the template to use.  We add the turtleNames array to the context dictionary with a key of turtles and then include the context dictionary when we render the list template.
If we look back at the list template, we see that we use the turtle array like this: {{ turtles.count }}.  This code calls the count property on the array to print out the number 4 since there are four elements in our turtleNames array.
Now let’s change the list.stencil file so we will print out the list of turtle names.

<html>
  <body>
    There are {{ turtles.count }} turtles.
    <ul>
    {% for turtle in turtles %}
      <li> {{ turtle }} </li>
    {% endfor %}
    </ul>
  </body>
</html>

Notice the new code that is within the {% and %}.  The line with the for turtle in turtles creates a for loop to iterate though the turtles array.  We then display each element in the array with the {{ turtle }} line and end the for loop with the {% endfor %} line.

Here is the full listing of the main.swift file:

import Kitura
import KituraStencil

// Create a new router
let router = Router()

//Setup Stencil
router.setDefault(templateEngine: StencilTemplateEngine())

router.get("/dontdo") {
    request, response, next in
    response.send("<html><body>")
    response.send("<h1>Don't do this</h1>'")
    response.send("</body></html>")
    next()
}

router.get("/list") {
    request, response, next in
    defer { next() }
    var context = [String: Any]()
    let turtleNames = [
        "Donetello", "Leonardo", "Michelangelo","Raphael"
    ]

    context["turtles"] = turtleNames
    try response.render("list", context: context)
}

// Add an HTTP server and connect it to the router
Kitura.addHTTPServer(onPort: 8090, with: router)

// Start the Kitura runloop (this call never returns)
Kitura.run()

It is pretty easy to use the Kitura Stencil framework.  In future posts well will be expanding on this knowledge.


No comments:

Post a Comment