Swift, Nginx, Perfect, & PerfectMustache
Posted by vonnagy on Mar 07 2020, in kraxn
This is a quick start guide on getting started with Swift and the Perfect framework. In a previous guide, we showed how to run Swift Perfect and Nginx together: Perfect handles the dynamic code, and Nginx handles the static files.
Today we are going to use the templating system within Perfect, called PerfectMustache. This is a more involved guide that has a lot of moving parts. If unfamiliar with templating systems, watch [Ray Wenderlich’s excellent introduction to PerfectMustache][1]). Its a great back up if you are ever stuck on anything here! Also check out [Perfect’s perfect blog template][2]).
One thing to note – I mostly just show the code, which very little explanation. I encourage you to fill in the gaps. This is mostly to try to keep things moving along. Check out Swift’s documentation on sections you don’t understand.
Will create 2 very basic webpages – the first will loop through names of birds, the second will display more info on the bird.
Here’s a summary of steps to accomplish this task:
- Install PerfectMustache
- Create Mustache Templates & static files
- Set up Swift Template
- Create PerfectMustache Functions
- Create Handlers for our pages
- Create a new routes
- Configure Nginx to handle Mustache So put on your favourite music and grab your favourite beverage – let’s do this 🙂
Install PerfectMustache
Check the instructions at [Github](https://github.com/PerfectlySoft/Perfect-Mustache) if you run into any issues. Within your PerfectTemplate folder you should see a Package.swift file, open this and add the dependency and target configuration.
- Dependency: .package(url: “https://github.com/PerfectlySoft/Perfect-Mustache.git”, from: “3.0.0”)
- Target Configuration: .target(name: “PerfectTemplate”, dependencies: [“PerfectHTTPServer”, “PerfectMustache”])
Your Package.swift should look something like this:
// swift-tools-version:4.2
import PackageDescription
let package = Package(
name: “PerfectTemplate”,
products: [
.executable(name: “PerfectTemplate”, targets: [“PerfectTemplate”])
],
dependencies: [
.package(url: “https://github.com/PerfectlySoft/Perfect-
HTTPServer.git, from: “3.0.0”),
.package(url: “https://github.com/PerfectlySoft/Perfect-Mustache.git”, from: “3.0.0”),
],
targets: [
.target(name: “PerfectTemplate”, dependencies: [“PerfectHTTPServer”, “PerfectMustache”])
]
)
Create Mustache Templates & static files
In our last tutorial, we create a virtual directory on our local machine at /var/www/swift.local/html. You can download the [base files from my site](https://kraxn.io/files/perfectmustache+images.tar.gz), it contains the follow:
- mustache folder containing index.mustache, and style.mustache. These don’t have mustache markup yet, we are going to write them in.
- images folder containing sparrow.jpg, starling.jpg, swallow.jpg, swift.jpg.
Just extract the perfectmustache+images.tar.gz directly into the html folder.
Set up Swift Template.
We will be using the template from the last Perfect/Nginx tutorial: We before we get to the guts of Mustaching, Routing and Handling, we have to set up few things:
- Add the PerfectMustache Library: import PerfectMustache
- Add the path to document root: we will use this path to access the mustache templates as an immutable variable.
- Add a data dictionary – This is an array containing dictionaries of birds. The image files you added earlier are needed for this.
- Delete the contents of func handler. We will be working on later.
This is our updated swift.main:
import PerfectHTTP
import PerfectHTTPServer
import PerfectMustache
//our document root<
let docRoot = “/var/www/swift.local/html/”
//our bird data
var birds = [
[<a href="">name</a>“sparrow”, <a href="">family</a>“Passeridae”, <a href="">wiki</a>“https://en.wikipedia.org/wiki/Old_World_sparrow”, <a href="">image</a>“sparrow.jpg”, <a href="https://kraxn.io/true">progLang</a>,
[<a href="">name</a>“starling”, <a href="">family</a>“Sturnidae”, <a href="">wiki</a>“https://en.wikipedia.org/wiki/Starling”, <a href="">image</a>“starling.jpg”, <a href="https://kraxn.io/false">progLang</a>,
[<a href="">name</a>“swallow”, <a href="">family</a>“Hirundinidae”, <a href="">wiki</a>“https://en.wikipedia.org/wiki/Swallow”, <a href="">image</a>“swallow.jpg”, <a href="https://kraxn.io/false">progLang</a>,
[<a href="">name</a>“swift”, <a href="">family</a>“Apodidae”, <a href="">wiki</a>“https://en.wikipedia.org/wiki/Swift”, <a href="">image</a>“swift.jpg”, <a href="https://kraxn.io/true">progLang</a>,
]
func handler(request:HTTPRequest, response:HTTPResponse) {
//swift code arriving soon!
}
var routes = Routes()
routes.add(method: .get, uri: “/”, handler: handler)
try HTTPServer.launch(name: “localhost”,
port: 8181,
routes: routes,
responseFilters: [(PerfectHTTPServer.HTTPFilter.contentCompression(data: [:]), HTTPFilterPriority.high)])
Perfect Mustache Function
Here is a stuct containing the function for interlacing Swift code and mustache template code. This code was taken verbatim from [Ray Wenderlich’s youtube video on PerfectMustache](https://www.youtube.com/watch?v=V98kYKD_R7s).
struct MustacheHelper: MustachePageHandler{
var values: MustacheEvaluationContext.MapType
func extendValuesForResponse(context contxt: MustacheWebEvaluationContext, collector: MustacheEvaluationOutputCollector) {
contxt.extendValues(with: values)
do {
try contxt.requestCompleted(withCollector: collector)
} catch {
let response = contxt.webResponse
response.appendBody(string: “(error)”)
.completed(status: .internalServerError)
}
}
}
Let’s add this to main.swift:
import PerfectHTTP
import PerfectHTTPServer
import PerfectMustache
//our document root
let docRoot = “/var/www/swift.local/html/”
//our bird data
var birds = [
[<a href="">name</a>“sparrow”, <a href="">family</a>“Passeridae”, <a href="">wiki</a>“https://en.wikipedia.org/wiki/Old_World_sparrow”, <a href="">image</a>“sparrow.jpg”, <a href="https://kraxn.io/true">progLang</a>,
[<a href="">name</a>“starling”, <a href="">family</a>“Sturnidae”, <a href="">wiki</a>“https://en.wikipedia.org/wiki/Starling”, <a href="">image</a>“starling.jpg”, <a href="https://kraxn.io/false">progLang</a>,
[<a href="">name</a>“swallow”, <a href="">family</a>“Hirundinidae”, <a href="">wiki</a>“https://en.wikipedia.org/wiki/Swallow”, <a href="">image</a>“swallow.jpg”, <a href="https://kraxn.io/false">progLang</a>,
[<a href="">name</a>“swift”, <a href="">family</a>“Apodidae”, <a href="">wiki</a>“https://en.wikipedia.org/wiki/Swift”, <a href="">image</a>“swift.jpg”, <a href="https://kraxn.io/true">progLang</a>,
]
//Mustache function
struct MustacheHelper: MustachePageHandler{
var values: MustacheEvaluationContext.MapType
func extendValuesForResponse(context contxt: MustacheWebEvaluationContext, collector: MustacheEvaluationOutputCollector) {
contxt.extendValues(with: values)
do {
try contxt.requestCompleted(withCollector: collector)
} catch {
let response = contxt.webResponse
response.appendBody(string: “(error)”)
.completed(status: .internalServerError)
}
}
}
func handler(request: HTTPRequest, response:HTTPResponse) {
//swift code arriving soon!
}
var routes = Routes()
routes.add(method: .get, uri: “/”, handler: handler)
try
HTTPServer.launch(name: “localhost”,
port: 8181,
routes: routes,
responseFilters: [(PerfectHTTPServer.HTTPFilter.contentCompression(data: [:]), HTTPFilterPriority.high)])
Modify our default handler and add mustache markup
Let’s change our default handler; this already has a route linked to it which is the root page (or homepage). In summary, we will:
- map var values so they can squeeze into our mustasche template
- assign our birds array to the values
- pass this information to our MustacheHelper. Here it’s important to note that we created the docRoot variable so we can find our mustache files.
Here’s our the handler code:
func handler(request:HTTPRequest, response:HTTPResponse) {
var values = MustacheEvaluationContext.MapType()
values[“birds”] = birds
mustacheRequest(request: request, response: response, handler: MustacheHelper(values: values), templatePath: docRoot+“mustache/index.mustache”)
}
Now that we passed the array to our MustacheHelper, we need to make sure that our index.mustache template receives it. In our our case, we only want to grab the bird name. Here we loop through the birds data, just grabbing the names from our dictionary. We create a new link to /hello{{name}} – we will create this page next. Here’s what it should look like:
<!doctype html>
<head>
<meta charset="utf-8">
<title>{{name}}</title>
</head>
<body>
<div id="content">
{{#birds}}
<div style="padding-bottom: 20px; border-bottom: 1px solid black">
<h4><a href="/hello/{{name}}">{{name}}</a></h4>
</div>
{{/birds}}
</div>
</body>
</html>
to compile and start the server:
swift build
.build/debug/PerfectTemplate
If everything went according to plan, you should be able to go to your http://swift.local and see this:
Create a new route, handler, and mustache mark up.
Let’s create that info page. First thing we should is create a route that will take us to the page. This will take us to the url ‘hello/{name}’‘, where name will be passed from page in the url.
routes.add(method: .get, uri: "hello/{name}", handler: birdHandler)
This a new handler, which we name birdHandler. Here’s what this does in summary:
- captures the response “name” with error catching
- loop through birds array to find the request bird
- if requested bird is found, return the entire dictionary back to mustache
func birdHandler(request: HTTPRequest, _ response: HTTPResponse) {
guard let name = request.urlVariables[“name”] else { response.completed(status: .badRequest) return
}
var values = MustacheEvaluationContext.MapType()
for item in birds {
if ( item[“name”] as? String == name )
{
values = item
}
}
mustacheRequest(request: request, response: response, handler: MustacheHelper(values: values), templatePath: docRoot+“mustache/index.mustache”)
}
Now, lets do the mustache markup. We are going to be reusing the index page. Here is a summary:
- Our hello/{{name}} doesn’t have the birds array, so we are going to provide a conditional statement to catch this.
- We can now populate the page with data from our specific bird.
- We have a conditional state on progLang to see if our bird name is a programming language or not.
<!doctype html>
<head>
<meta charset="utf-8">
<p><title>{{name}}</title><br />
{{> style}} <br />
</head>
<body></p>
<p> <div id="content">
{{#birds}}
<div style="padding-bottom: 20px; border-bottom: 1px solid black">
<h4><a href="/hello/{{name}}">{{name}}</a></h4>
</div>
{{/birds}}
{{^birds}}
<h2>My bird name is {{name}}</h2>
<p>Family: {{family}} </p>
<p>Wiki Link: <a href="{{wiki}}">{{name}} info</a></p>
<p><img src="/images/{{image}}"> </p>
<p></p>
<p> {{#progLang}}
{{name}} is also a programming language.
{{/progLang}}</p>
<p> {{^progLang}}
{{name}} is sadly not a programming language.
{{/progLang}}</p>
</p>
a complete list of <a href="/">birds</a>
{{/birds}}
</div>
</body>
</html>
to compile and start the server:
swift build
.build/debug/PerfectTemplate
If everything went according to plan, you should be able to go to your http://swift.local/hello/starling and see this:
Adding ‘mustache’ static files to Nginx
Finally, .mustache files are still served by Perfect server. Let’s move those over to Nginx, since they are static files. Change this line in your /etc/nginx/conf.d/swift.local.conf :
# serve static files
location ~ ^/(images|javascript|js|css|mustache|media|static)/ {
root /var/www/swift.local/html;
expires 30d;
}
afterwards test and reload:
sudo nginx -t
sudo systemctl reload nginx
Viola! Here is the final main.swift file for reference:
import PerfectHTTP
import PerfectHTTPServer
import PerfectMustache
let docRoot = “/var/www/swift.local/html/”
var birds = [
[<a href="">name</a>“sparrow”, <a href="">family</a>“Passeridae”, <a href="">wiki</a>“https://en.wikipedia.org/wiki/Old_World_sparrow”, <a href="">image</a>“sparrow.jpg”, <a href="https://kraxn.io/true">progLang</a>,
[<a href="">name</a>“starling”, <a href="">family</a>“Sturnidae”, <a href="">wiki</a>“https://en.wikipedia.org/wiki/Starling”, <a href="">image</a>“starling.jpg”, <a href="https://kraxn.io/false">progLang</a>,
[<a href="">name</a>“swallow”, <a href="">family</a>“Hirundinidae”, <a href="">wiki</a>“https://en.wikipedia.org/wiki/Swallow”, <a href="">image</a>“swallow.jpg”, <a href="https://kraxn.io/false">progLang</a>,
[<a href="">name</a>“swift”, <a href="">family</a>“Apodidae”, <a href="">wiki</a>“https://en.wikipedia.org/wiki/Swift”, <a href="">image</a>“swift.jpg”, <a href="https://kraxn.io/true">progLang</a>,
]
struct MustacheHelper: MustachePageHandler{
var values: MustacheEvaluationContext.MapType
func extendValuesForResponse(context contxt: MustacheWebEvaluationContext, collector: MustacheEvaluationOutputCollector) {
contxt.extendValues(with: values)
do {
try contxt.requestCompleted(withCollector: collector)
} catch {
let response = contxt.webResponse
response.appendBody(string: “(error)”)
.completed(status: .internalServerError)
}
}
}
func handler(request:HTTPRequest, response:HTTPResponse) {
var values = MustacheEvaluationContext.MapType()
values[“birds”] = birds
mustacheRequest(request: request, response: response, handler: MustacheHelper(values: values), templatePath: docRoot+“mustache/index.mustache”)
}
func birdHandler(request:HTTPRequest, _ response:HTTPResponse) {
guard let name = request.urlVariables[“name”] else {
response.completed(status: .badRequest)
return
}
var values = MustacheEvaluationContext.MapType()
for item in birds {
if ( item[“name”] as? String == name )
{
values = item
}
}
mustacheRequest(request: request, response: response, handler: MustacheHelper(values: values), templatePath: docRoot+“mustache/index.mustache”)
}
var routes = Routes()
routes.add(method: .get, uri: “/”, handler: handler)
routes.add(method: .get, uri: “hello/{name}”, handler: birdHandler)
try HTTPServer.launch(name: “localhost”,
port: 8181,
routes: routes,
responseFilters: [
(PerfectHTTPServer.HTTPFilter.contentCompression(data: [:]), HTTPFilterPriority.high)])
Images were taken from wiki commons – here is the attribution
- sparrow: Fir0002 / GFDL1.2 (http://www.gnu.org/licenses/old-licenses/fdl-1.2.html)
- starling: Tim Felce (Airwolfhound) / CC BY-SA (https://creativecommons.org/licenses/by-sa/2.0)
- swallow: Axel Strauß / CC BY-SA (http://creativecommons.org/licenses/by-sa/3.0/)
- swift: Paweł Kuźniar (Jojo_1, Jojo) / CC BY-SA (http://creativecommons.org/licenses/by-sa/3.0/)