IBM製 Swift Webアプリケーションフレームワーク&HTTPサーバ Kitura最速マスター

# はじめに

![Kitura](https://raw.githubusercontent.com/IBM-Swift/Kitura/master/Documentation/KituraLogo-wide.png)

2016年2月22日（日本時間23日未明）、IBMのクラウドプラットフォーム**Bluemix**がSwiftに対応したことを発表しました。加えて、**Kitura**と呼ばれるSwiftのWebアプリケーションフレームワークがOSSとして公開されました。

IBMによるSwift関連の取り組みはオフィシャルサイトとGithub上のリポジトリで確認することができます。

* https://github.com/IBM-Swift
* https://developer.ibm.com/swift/products/kitura/

## なぜIBMはSwiftを選ぶのか？

[Swift@IBM](https://developer.ibm.com/swift)の[Questions and Answers](https://developer.ibm.com/swift/q-a/)で、What is Swift?という問いに対して、以下のように答えていることから、Swiftを将来性のあるプログラミング言語と捉えて、自社のプロダクトに採用しているのだと思います。

* Swiftは強力かつ簡潔なプログラミング言語
* Swfitを書くのはインタラクティブだし楽しい
* Swiftの文法は簡潔なのに奥深い
* Swiftで書かれたアプリケーションは速い(lightning-fast)

## イントロ

環境はMac OS Xを想定しています。以下の紹介項目は、ゆーすけべーさんの[Mojolicious最速マスター](http://qiita.com/yusukebe/items/65141c9c63a2860af962)に影響を受けています。

ここで紹介したKituraのサンプルコードは[こちら](https://github.com/akimacho/Kitura-Sampler)です。紹介できなかった**SwiftRedis**のサンプルコードもあります。

なお、Kituraの開発は現在進行中なので、以下の紹介する項目は動かなくなってしまうかもしれません。その場合はKituraのレポジトリをご確認ください。

* [IBM-Swift/Kitura](https://github.com/IBM-Swift/Kitura)

### インストールする

Kituraを使うには、以下の2点が必要になります。

* Kituraに必要なライブラリのインストール
* 最新のSwiftコンパイラのインストール

#### 必要なライブラリのインストール

必要なライブラリのインストールには**Homebrew**を使います。Homebrewのインストール方法や使い方は以下の記事がわかりやすいと思います。

* [Homebrew はじめの一歩 - Qiita](http://qiita.com/oreo3@github/items/73d5cd80781a099622a8)

`brew install`コマンドを使って次のライブラリをインストールします。

* **http-parser** ― HTTPパーサライブラリ
* **pcre2** ― Perl互換の正規表現ライブラリ
* **curl** ― クライアントサイド通信ライブラリ
* **hiredis** ― Redisクライアントライブラリ

```
$ brew install http-parser pcre2 curl hiredis
```

#### 最新のSwiftコンパイラのインストール

最新のSwiftコンパイラをインストールします。以下のサイトにアクセスし、**Xcode Swift Development Snapshot**からコンパイラをダウンロードします。そして、ダウンロードしたpkgファイルからコンパイラをインストールします。

https://swift.org/download/#latest-development-snapshots

パスを通して、コンパイラを使えるようにします。`.bash_profile`や`.zshrc`などのシェル設定ファイルにパスを記述します。`source`コマンドで設定を反映させましょう。

```
export PATH="/Library/Developer/Toolchains/swift-latest.xctoolchain/usr/bin:$PATH"
```

###### 更新メモ

最新コンパイラではなく、**swift-DEVELOPMENT-SNAPSHOT-2016-02-08-a**（以下のリンク）を使ったほうがよいみたいです。[最新コンパイラ(swift-DEVELOPMENT-SNAPSHOT-2016-02-25-a)ではKituraをビルドできないみたいです](https://github.com/IBM-Swift/Kitura/pull/180#issuecomment-189457622)。確かに、02-08版と02-25版ではSwift Package Managerの挙動が変わっていてmakeできませんでした。なので、以下のコンパイラを使ったほうがよいかもしれません。

https://swift.org/builds/development/xcode/swift-DEVELOPMENT-SNAPSHOT-2016-02-08-a/swift-DEVELOPMENT-SNAPSHOT-2016-02-08-a-osx.pkg

### 雛形を作る

**Swift Package Manager**というSwiftアプリ作成のツールを使います。詳しくは`swift build --help`を確認してください。`swift build --init`でスケルトンを作成します。

```
$ mkdir MyApp 
$ cd MyApp
$ swift build --init
```

ファイル構成は以下のようになります。

```
$ tree -al MyApp/
MyApp/
├── .gitignore
├── Package.swift
├── Sources
│   └── main.swift
└── Tests
```

プロジェクト関連のファイルは`Sources`、テスト関連のファイルは`Tests`以下に作成します。また、プロジェクトの依存関係の記述は`Package.swift`に行います。Kituraを利用するには、この`Package.swift`に依存関係を記述していきます。

```swift:Package.swift
import PackageDescription

let package = Package(
    name: "MyApp",
    dependencies: [
        .Package(url: "https://github.com/IBM-Swift/Kitura-router.git", versions: Version(0,2,0)..<Version(0,3,0)),
    ]
)
```

上のように`Package.swift`を記述したあとに、`swift build`により依存するライブラリをインストールします。`error: exit(1) ...`と表示されますがライブラリのインストールには成功しているようなので、スルーしてよいことに注意してください。

```
$ swift build
Cloning https://github.com/IBM-Swift/Kitura-router.git
Using version 0.2.0 of package Kitura-router
Cloning https://github.com/IBM-Swift/Kitura-net.git
Using version 0.2.0 of package Kitura-net
(略)
<unknown>:0: error: link command failed with exit code 1 (use -v to see invocation)
<unknown>:0: error: build had 1 command failures
**error:** exit(1): ["/Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2016-02-08-a.xctoolchain/usr/bin/swift-build-tool", "-f", "/Users/akimacho/MyApp/.build/debug/MyApp.o/llbuild.yaml"]
```

最後にプロジェクトをビルドするためのMakefileをコピーします。

```
$ cp Packages/Kitura-net-0.3.0/Makefile-client Makefile
```

以上でKituraを使う準備が完了しました！

### 立ち上げてみる

実際にWebアプリを立ち上げてみます。`MyApp/Sources/main.swift`に、以下のKituraのサンプルコードを書いてみましょう。

```swift:main.swift
import KituraRouter
import KituraNet
import KituraSys

let router = Router()
router.get("/") { request, response, next in 
    next()
}
let server = HttpServer.listen(8090, delegate: router)
Server.run()
```

プロジェクトをビルドします。

```
$ make
```

Webアプリを実行します。ビルドした実行ファイルは`MyApp/.build/debug/MyApp`にあります。
もしも「**アプリケーション"MyApp"へのネットワーク受信接続を許可しますか？**」というウィンドウが表示された場合、許可ボタンを押しましょう。

```
$ .build/debug/MyApp

```

ブラウザで http://localhost:8090/ にアクセスすると、以下のようなページが表示されます。

![Kitura_Router.png](https://qiita-image-store.s3.amazonaws.com/0/53701/2b7bb649-daee-aa35-9074-a47ae1bddcfe.png)


## Kituraアプリの基本

まず基本的には`KituraRouter`、`KituraNet`、`KituraSys`をインポートします。これらはURLのルーティングやWebサーバに必要なライブラリです。次に、Routerのインスタンスに対してルーティング情報とそれに対応する処理などを登録します。

```swift:main.swift
import KituraRouter
import KituraNet
import KituraSys

let router = Router()
// ルーティング情報の登録
// 他のルーティング情報を登録する場合、"/"の情報は最後に登録します
router.get("/") { request, response, next in 
    response.setHeader("Content-Type", value: "text/html; charset=utf-8")
    do {
        try response
            .status(HttpStatusCode.OK)      // ステータスコードを設定します
            .send("<p>Hello, world!</p>")   // 文字列を返します
            .end()                          // レスポンスを終了します
    }
    catch {
        // エラー処理
        print("Response Error!")
    }
    // next()を呼び出すことで、次のURLに対応した処理の準備に移ります
    next()
}
// ポート番号とルータを設定し、サーバを起動します
let server = HttpServer.listen(8090, delegate: router)
Server.run()
```

********

## ルーティング

### GET/POSTのメソッドで振り分ける

Routerオブジェクトの`get()`と`post()`を使います。他にも、`put()`や`delete()`が用意されています。
`getメソッド`と`postメソッド`により、URLとURLに対応した処理のコールバック（クロージャ）を登録することができます。

```swift
let router = Router()
router.get("/entry") { request, response, next in 
    // ...
}
// 以下のように書いても同じです
router.get("/entry", handler: { request, response, next in 
    // ...
})
router.post("/entry") { request, response, next in 
    // ...
}
```

### URLの一部をキャプチャする

`/entry/{数字}`の数字部分のようなURLの一部をキャプチャしたい場合は、辞書型の`request.params`にアクセスします。

```swift
router.get("/entry/:id") { request, response, next in
    let id = request.params["id"]!
    // ...
}
```

********

## コントローラ

### リクエストパラメータ値を取得する

辞書型の`request.body!.asUrlEncoded()!`を使います。

```swift
// リクエストパラメータを取得するためのmiddlewareを指定
router.use("/*", middleware: BodyParser())
router.post("/entry") { request, response, next in
    let queryParams = request.body!.asUrlEncoded()!
    let title = queryParams["title"]!
    let body = queryParams["body"]
    // ...
}
```

### デバグ用のログを出力する

`LoggerAPI`と`HeliumLogger`を使うことで、ログレベルに合わせたログ出力を行うことができます。
`Package.swift`を以下のように編集します。

```swift:Package.swift
import PackageDescription

let package = Package(
    name: "MyApp",
    dependencies: [
        .Package(url: "https://github.com/IBM-Swift/Kitura-router.git", versions: Version(0,2,0)..<Version(0,3,0)),
        .Package(url: "https://github.com/IBM-Swift/HeliumLogger.git", versions: Version(0,2,0)..<Version(0,3,0)),
        .Package(url: "https://github.com/IBM-Swift/LoggerAPI.git", versions: Version(0,2,0)..<Version(0,3,0)),
    ]
)
```

サンプルコードです。

```swift:main.swift
import KituraSys
import KituraNet
import KituraRouter
import LoggerAPI
import HeliumLogger

let router = Router()
Log.logger = HeliumLogger()// ログ出力用オブジェクトを移譲します
router.get("/") { request, response, next in
     Log.debug("This is debug log!")
     Log.verbose("This is verbose log!")
     Log.info("This is info log!")
     Log.warning("This is warning log!")
     Log.info("This is error log!")
     next()
}
let server = HttpServer.listen(8090, delegate: router)
Server.run()
```

以下のように出力されました（実際は色付きです）。`Log.debug("This is debug log!")`の出力結果は、`WARNING`レベル扱いになっていました。移譲先のHeliumLoggerでそのように実装されているようです。

```:実行結果
 WARNING: initialize() /Users/akimacho/MyApp/Packages/Kitura-router-0.2.0/Sources/KituraRouter/contentType/ContentType.swift line 72 - Loading embedded MIME types.
 VERBOSE: init() /Users/akimacho/MyApp/Packages/Kitura-router-0.2.0/Sources/KituraRouter/Router.swift line 43 - Router initialized
 INFO: spiListen(_:port:) /Users/akimacho/MyApp/Packages/Kitura-net-0.2.0/Sources/KituraNet/HttpServerSpi.swift line 46 - Listening on port 8090
 INFO: spiListen(_:port:) /Users/akimacho/MyApp/Packages/Kitura-net-0.2.0/Sources/KituraNet/HttpServerSpi.swift line 51 - Accepted connection from: 127.0.0.1on port 36082
 VERBOSE: MyApp /Users/akimacho/MyApp/Sources/main.swift line 9 - This is verbose log!
 WARNING: MyApp /Users/akimacho/MyApp/Sources/main.swift line 10 - This is debug log!
 INFO: MyApp /Users/akimacho/MyApp/Sources/main.swift line 11 - This is info log!
 WARNING: MyApp /Users/akimacho/MyApp/Sources/main.swift line 12 - This is warning log!
 ERROR: MyApp /Users/akimacho/MyApp/Sources/main.swift line 13 - This is error log!
 INFO: spiListen(_:port:) /Users/akimacho/MyApp/Packages/Kitura-net-0.2.0/Sources/
```

### テンプレートをレンダリングする

後ほど説明しますが、Kitura（のサンプルコード）では、Mustache形式のテンプレートエンジンGRMustache.swiftを採用しています。Mustacheをはじめて聞く方もいらっしゃるのではないでしょうか？私はKituraを通してはじめて知りました。一般的なテンプレートエンジン（例えば、erbでしょうか）とは違いMustacheは**Logic-less templates**と呼ばれていて一風変わっていますが、慣れると使いやすくなっていくと思います。
さて、GRMustache.swiftを使ったテンプレートのレンダリング手順は以下のようになります。

1. `Template(...)`でテンプレート作成
1. `Box(...)`でテンプレートに変数の反映(埋め込み)
1. `Template.render(...)`でHTMLテキスト作成
1. `try! response.status(HttpStatusCode.OK).send(...).end()`でHTMLドキュメントを返す

Mustache形式に則った文字列をHTML形式の文字列に変換したあと`response.status(HttpStatusCode.OK).send()`に渡すという手順を踏むことになります。
最初に、`Package.swift`の依存関係にGRMustache.swiftを加えましょう。

```swift:Package.swift
import PackageDescription

let package = Package(
    name: "MyApp",
    dependencies: [
        .Package(url: "https://github.com/IBM-Swift/Kitura-router.git", versions: Version(0,2,0)..<Version(0,3,0)),
        .Package(url: "https://github.com/IBM-Swift/GRMustache.swift.git", majorVersion: 1), 
    ]
)
```

以下のサンプルコードは、URLの一部をキャプチャして、"Hello, ○○"と返すプログラムです。例えば、`http://localhost:8090/lattner`にアクセスした場合、"Hello, lattner!!"というテキストが返ってきます。

```swift:main.swift
import KituraRouter
import KituraNet
import KituraSys
import Mustache

let router = Router()
router.get("/:name") { request, response, next in
    let name = request.params["name"]!
    // テンプレート作成
    let template = try! Template(string: "Hello, {{name}}!!")
    let data = ["name" : name]
    // 変数の反映
    let boxedData = try! Box(data)
    // HTMLテキスト作成
    let html = try! template.render(boxedData)
    try! response.status(HttpStatusCode.OK).send(html).end()
    next()
}
let server = HttpServer.listen(8090, delegate: router)
Server.run()
```

```:実行結果
$ curl http://localhost:8090/rfdickerson
Hello, rfdickerson!!
```

### JSONを出力する

オブジェクトをJSON形式にエンコードする`SwiftyJSON`はデフォルトでインストールされているので、これをインポートするだけで`SwiftyJSON`を使うことができます。エンコードした得られた文字列を`response.status(HttpStatusCode.OK).sendJson(json)`で返します。`sendJson(...)`を使うことで、自動的にレスポンスヘッダに"Content-Type: application/json; charset=utf-8"が付加されます。

```swift:main.swift
import KituraRouter
import KituraNet
import KituraSys
import SwiftyJSON

let router = Router()
router.get("/") { request, response, next in
    let entries = [
        ["title":"apple", "content":"swift is a programming language"], 
        ["title":"Ringo Starr", "content":"Ringo Starr is a drummer"], 
    ]
    // JSON形式にエンコードする
    let json = JSON(entries)
    // Content-Type: application/json; charset=utf-8として返す
    try! response.status(HttpStatusCode.OK).sendJson(json).end()
    next()
}
let server = HttpServer.listen(8090, delegate: router)
Server.run()
```

```:実行結果
$ curl -v http://localhost:8090/
(略)
< HTTP/1.1 200 OK
< Content-Type: application/json
< Content-Length: 165
< Date: Tue, 23 Feb 2016 13:23:59 GMT
<
(略)
$ curl http://localhost:8090/
[
  {
    "title" : "apple",
    "content" : "swift is a programming language"
  },
  {
    "title" : "Ringo Starr",
    "content" : "Ringo Starr is a drummer"
  }
]
```

### リダレイクトさせる

`response.redirect()`を使います。パスだけだと同一アプリ内、URLだとそのURLへリダイレクトされます。

```swift
router.get("/dummy") { request, response, next in
    try! response.redirect("/")
    next()
}
router.get("/example") { request, response, next in
    try! response.redirect("http://example.com/")
    next()
}
```

********

## テンプレート

ここでは、テンプレートGRMustacheの構文について取り上げます。より詳しく情報は[GRMustache.swiftのリポジトリ](https://github.com/groue/GRMustache.swift)を確認してください。

### 変数の展開

単純に変数を展開します。`{{value}}`の場合はHTMLエスケープ、`{{{value}}}`の場合はHTMLエスケープを行いません。

```swift
let template = try! Template(string: "{{text}} , {{{text}}}")
let data = ["text" : "<p>Hello</p>"]
let boxedData = Box(data)
let html = try! template.render(boxedData)
// => &lt;p&gt;Hello&lt;/p&gt; , <p>Hello</p>
```

### 条件分岐

条件に応じてマークアップを分岐させます。以下のテキストの場合`value`が**true**ならば`{{content}}`をマークアップします。

```
{{#value}}
    {{content}}
{{/value}}
```

また、`value`が**true**でない場合も取り扱うには以下のようにします

```
{{#value}}
    {{content}}
{{/value}}
{{^value}}
    {{otherwise}}
{{/value}}
```

```swift
let text =    "{{title}}" + "{{#showLink}} {{link}} {{/showLink}}"
let template = try! Template(string: text)
let data1 = ["title" : "Hoge", "link" : "http://example.com", "showLink" : true]
let data2 = ["title" : "Foo", "link" : "http://example.jp", "showLink" : false]
let boxedData1 = Box(data1)
let boxedData2 = Box(data2)
let html1 = try! template.render(boxedData1) //=> Hoge http://example.com 
let html2 = try! template.render(boxedData2) // => Foo
```

テンプレートにおいてfalse扱いされるオブジェクトは以下の通りです。

* 値が格納されていない
* false
* 0
* 空文字("")
* 空配列([])、空辞書([:])
* NSNull

### 辞書の展開

`{{#value}} ... {{/value}}`のvalueが辞書に対するキーになっている場合、配列の要素を順番にアクセスしていきます。

```swift
let text = "{{#book}}"
            + "{{author}}, {{date}}, {{title}}"
            + "{{/book}}"
let template = try! Template(string: text)
let data = [
    "book" : [
        "title" : "C Programming Language", 
        "author" : "Brian W. Ritchie, Dennis Kernighan", 
        "date" : "1988/3/22", 
    ]
]
let boxedData = Box(data)
let html = try! template.render(boxedData)
//=> Brian W. Ritchie, Dennis Kernighan, 1988/3/22, C Programming Language
```

### 配列の展開

また、`{{#value}} ... {{/value}}`のvalueが配列に対するキーになっている場合、配列の要素を順番にアクセスしていきます。

```swift
let text = "<ul>\n{{#links }}"
            + "<li><a href=\"{{link}}\">{{title}}</a></li>\n"
            + "{{/links }}</ul>\n"
let template = try! Template(string: text)
let data = [
    "links" : [
        ["title": "IBM", "link" : "http://ibm.com" ], 
        ["title": "Github", "link" : "https://github.com"], 
        ["title": "Qiita", "link" : "http://qiita.com"], 
    ]
]
let boxedData = Box(data)
let html = try! template.render(boxedData)
/*=>
<ul>
<li><a href="http://ibm.com">IBM</a></li>
<li><a href="https://github.com">Github</a></li>
<li><a href="http://qiita.com">Qiita</a></li>
</ul>

*/
```

### レイアウトの適応、外部テンプレートファイルの使用

`{{> partial }}`は、テンプレート内にテンプレートpartialを格納します。これを使うことで、レイアウトを適用することができます。ついでに、外部テンプレートファイルを使ったレンダリング方法についても取り上げます。外部テンプレートをファイルを使うことで、ビューとロジックを分離することができます。
まずテンプレートファイルを配置するディレクトリ`Statics`を`MyApp`以下に作成します。次に、生成するHTMLのレイアウト用テンプレートの`layout.mustache`とコンテンツ用のテンプレート`entry.mustache`を`Statics`以下に作成し、以下のコードを記述します。

```layout.mustache
{{! コメントです。layout.mustache }}
<html>
<head>
    <title>{{ title }}</title>
</head>
<body>
    {{# content }}
        {{> entry }}
    {{/ content }}
</body>
</html>
```

```entry.mustache
{{! コメントです。entry.mustache }}
<h1>{{{ header }}}</h1>
<p>{{{ text }}}</p>
```

以上の作業を通して、プロジェクトのファイル構成は以下のようになっていることを想定しています。

```
MyApp/
├── Makefile
├── Package.swift
├── Packages
│   ├── BlueSocket-0.0.4
│   ├── GRMustache.swift-1.0.1
│   ├── (略)
│   └── SwiftyJSON-2.3.3
├── Sources
│   └── main.swift
├── Statics
│   ├── entry.mustache
│   └── layout.mustache
└── Tests
```

実際にレンダリングさせてみましょう。以下のサンプルコードでは、テンプレートファイルが配置されているディレクトリのパスを`TemplateRepository(...)`に渡し、ファイル名を指定することでテンプレートを取得しています。

```swift:main.swift
import KituraRouter
import KituraNet
import KituraSys
import Mustache
import Foundation // NSFileManager

// Webアプリのカレントディレクトリを取得します
// サンプルコードの場合、basePathは $HOME/MyApp 
let basePath= NSFileManager.defaultManager().currentDirectoryPath
// テンプレートファイルが配置されているディレクトリのパスを指定する
let repository = TemplateRepository(directoryPath: basePath + "/Statics")
let router = Router()
router.get("/") { request, response, next in
    // Staticsディレクトリ内のlayout.mustacheのテンプレートを取得する
    let template = try! repository.template(named: "layout")
    let data = [
        "title" : "KItura", 
        "content": [
            "header": "Web application framework Kitura", 
            "text": "Build end-to-end apps using Swift with Kitura", 
        ]
    ]
    let html = try! template.render(Box(data))
    response.status(HttpStatusCode.OK).send(html)
    next()
}
let server = HttpServer.listen(8090, delegate: router)
Server.run()
```

`http://localhost:8090/`にブラウザでアクセスすると以下のように表示されました。

********

## その他

### セッションを使う

セッションに該当する機能は見つけられませんでした。

### ステータスコードを設定する

```swift
// 201を返す
response.status(HttpStatusCode.OK).send(html).end()
// 
try! response.status(HttpStatusCode. NOT_FOUND).send("Not Found").end()
```

### HTTPリクエストヘッダを取得する

HTTPヘッダの情報は、辞書型変数の`request.headers`内に格納されています。以下のサンプルコードでは、クライアントの**User-Agent**を取得するプログラムです。

```swift
router.get("/") { request, response, next in
    let userAgent = request.headers["User-Agent"]!
    print(userAgent)
    next()
}
let server = HttpServer.listen(8090, delegate: router)
Server.run()
```

`curl http://localhost:8090/`でアクセスした結果、`curl/7.43.0`と出力されました。

### HTTPレスポンスヘッダを設定する

```
// レスポンスヘッダを Content-type: text/html; charset=utf-8 とする
response.setHeader("Content-Type", value: "text/plain; charset=utf-8")
// IE対策
response.setHeader("X-Content-Type-Options", value: "nosniff")
```

********

## チュートリアル

Kituraを使ったTODOリストアプリ作成

* https://developer.ibm.com/swift/2016/02/22/building-end-end-cloud-apps-using-swift-kitura/

# おわりに

Swiftのようなモダンな型システムを持った言語でのWebアプリ作りははじめてだったので、Kituraは楽しいです。みなさんもぜひよかったら使ってみてはいかがでしょうか。今回省略してしまったエラー処理を補完したサンプルコードのレポジトリは以下のページにあります。

* https://github.com/akimacho/Kitura-Sampler

## 参考

* [［速報］IBMとアップル、プログラミング言語「Swift」をクラウド対応に。Bluemix上にSwiftランタイム、Webフレームワーク「Kitura」などを提供 － Publickey](http://www.publickey1.jp/blog/16/ibm_swift.html)
* [Kitura - Swift](https://developer.ibm.com/swift/products/kitura/)
* [IBM-Swift/Kitura: Web framework and HTTP server for Swift](https://github.com/IBM-Swift/Kitura)