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.
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.