SwiftのWebフレームワークであるKituraを使ったアプリ開発の始め方をここにまとめおきます。
事前準備
- Homebrewのインストール
ターミナルで下記のコマンドを実行しインストールします。後続のインストールに必要なパッケージマネージャーをインストールしておきます。
$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
- Curlのインストール
APIを叩く時にcurlコマンドは基本なので、下記のコマンドをターミナルで実行してインストールしておきます。
$ brew install curl
- Xcodeのインストール
実際にアプリを作成する際にはXcodeが便利。
こちらからXcodeのインストールをしておきます。
Workディレクトリを用意する
ターミナルからディレクトリを作成してカレントディレクトリを移動します。
$ mkdir myToDoList
$ cd myToDoList
Swift package Managerを使ってInitialize
Swift Package Managerを使って開発を行うための様々なライブラリをダウンロードし、依存関係も含めて用意してくれます。cocoapodsなどはiOS開発では使われていますが、サーバーサイドではおそらくこちらが主流となってくるかと思います。
$ swift package init
インストールされたライブラリを見てみます。
$ ls
Package.swift Sources Tests
ちなみに Package ManagerのHelpはこのコマンドで確認できるので紹介しておきます。
$ swift package init --help
Xcodeプロジェクトを起ち上げる
以下のコマンドでXcodeプロジェクトを生成する。
$ swift package generate-xcodeproj
Xcodeプロジェクトを立ち上げます。
$ open myToDoList.xcodeproj/
立ち上げると以下のような基本的なプロジェクト構造を持っていることがわかります。
.
├─ ─ Package.swift
├─ ─ Sources
│ └── myToDoList.swift
├─ ─ Tests
│ └── myToDoListTests
│ └── myToDoListTests.swift
└─ ─ myToDoList.xcodeproj
Kituraフレームワークを適用する
まずはKituraライブラリをインストールするために、Kitura Package Catalogにあるライブラリが必要となります。
ここで、検索バーでKituraといれるとKituraのフレームワークがみつかります。ひとまずこのライブラリのurlを右側のメニューからクリップにコピーします。
次にpackage.swiftを編集します。以下のようにコピーしたKituraライブラリのurlを貼り付けて、dependenciesを追加します。
package.swift
import PackageDescription
let package = Package(
name: "myToDoList",
dependencies: [
.Package(url: "https://github.com/IBM-Swift/Kitura", majorVersion: 1, minor: 6),
]
)
さらに、ログを取得するるためのフレームワークであるHeliumLoggerも同様にKitura Package Catalogから探してpackage.swiftに追加します。
package.swift
import PackageDescription
let package = Package(
name: "myToDoList",
dependencies: [
.Package(url: "https://github.com/IBM-Swift/Kitura", majorVersion: 1, minor: 6),
.Package(url: "https://github.com/IBM-Swift/HeliumLogger", majorVersion: 1, minor: 6)
]
)
Kitura フレームワークを適用する際のアプリ構造
Kituraを使ってWebAPIを構築する際、Routerクラスを生成する必要があります。このRouterクラスはアプリケーション構造のトップレベルでは使用できません。そのため、main.swiftとその従属するライブラリに分けてRouterクラスを使用します。そこでここでは、以下のようなアプリ構造にしていこうと思います。
.
├─ ─ Package.swift
├─ ─ Sources
├─ ─ ├─Server
└── main.swift
├─ ─ ├─myToDoList
└── myToDoList.swift
├─・・・
依存関係とpackage.swiftの書き方
以下にソースコードと依存関係を表記します。この依存関係に則って、package.swiftのターゲットを記す必要があります。
ソースの場所 | 依存関係 |
---|---|
Sources/A/main.swift | (which requires B) |
Sources/B/WebController.swift | (which requires C) |
Sources/C/DatabaseConnector.swift |
例:
import PackageDescription let package = Package(
name: "MyWebApp",
targets: [
Target(name: "A", dependencies: [.Target(name: "B")]),
Target(name: "B", dependencies: [.Target(name: "C")]),
Target(name: "C")]
この依存関係に従ってターゲットを以下のように編集します。
import PackageDescription
let package = Package(
name: "myToDoList",
targets: [
Target(name: "Server", dependencies: [.Target(name: "myToDoList")]),
Target(name: "myToDoList")
],
dependencies: [
.Package(url: "https://github.com/IBM-Swift/Kitura", majorVersion: 1, minor: 6),
.Package(url: "https://github.com/IBM-Swift/HeliumLogger", majorVersion: 1, minor: 6)
]
)
ライブラリ構成を変更する
現在のライブラリの構成は以下の通りとなっています。
.
├─ ─ Package.swift
├─ ─ Sources
└── myToDoList.swift
├─・・・
Finderからフォルダを作成して以下の通りにします。
.
├─ ─ Package.swift
├─ ─ Sources
├─ ─ ├─Server
├─ ─ ├─myToDoList
└── myToDoList.swift
├─・・・
ここで、メインのロジックを書くためのSources/Server/main.swiftを準備します。
$ touch Sources/Server/main.swift
これでライブラリ構成が整ったので、プロジェクトを再度生成し直しすためターミナルからregenerateします。
$ swift package generate-xcodeproj
Sourcesフォルダにライブラリが増えていることがわかると思います。
メインロジックの実装でHello World!
それではさっそく、myToDoListにあるコードを全選択して、カットしてください。
それをそのままmain.swiftに貼り付けます。
main.swift
import Kitura
let router = Router()
router.get("/") {
request, response, next in
response.status(.OK).send("Hello Wolrd")
next()
}
Kitura.addHTTPServer(onPort: 8090, with: router)
Kitura.run()
シミュレーターをサーバーに変更してBuildが成功したら、ブラウザを立ち上げます。
さっそくhttp://localhost:8090にアクセスしてみましょう。
おめでとうございます!Hello Worldが見えましたね!
LOGGERの実装
次にHeliumLoggerを実装します。
main.swift
import Kitura
import HeliumLogger
import LoggerAPI
let router = Router()
HeliumLogger.use()
router.get("/") {
request, response, next in
Log.info("Hello!Logger!")
response.status(.OK).send("Hello Wolrd")
next()
}
Kitura.addHTTPServer(onPort: 8090, with: router)
Kitura.run()
Buildして実行後、ターミナルからcurlコマンドを叩いてみます。
curl http://localhost:8090
Xcodeのコンソール上に以下のようなメッセージが出てきていれば問題ありません。
[2017-03-06T23:13:52.830+09:00] [VERBOSE] [HTTPServerRequest.swift:215 parsingCompleted()] HTTP request from=127.0.0.1; proto=http;
[2017-03-06T23:13:52.830+09:00] [INFO] [main.swift:11 Server] Hello!Logger!
ライブラリーを使ってmain.swiftを洗練する
main.swiftを軽くし、ライブラリ内の閉じた世界でテストできるように構成を変更します。
RouterをmyToDoListに持っていくと以下のようにスッキリさせます。
main.swift
import Kitura
import LoggerAPI
import myToDoList
HeliumLogger.use()
Kitura.addHTTPServer(onPort: 8090, with: router)
Kitura.run()
myToDoList.swift
import Kitura
import LoggerAPI
let router = Router()
router.get("/") {
request, response, next in
Log.info("Hello!Logger!")
response.status(.OK).send("Hello Wolrd")
next()
}
ここでHTTPServerとmyToDoListでLinkageなどが貼られていないので、Classを作成します。
Routerをhandlerを使ってAPIとして読み込めるようにしていきます。
ここではGetとPostを作っていくので以下のようにしました。
import Kitura
import LoggerAPI
public class myToDoList {
public let router = Router()
public init() {
router.get("/v1/tasks", handler: handleGetTasks)
router.post("/v1/tasks", handler: handleAddTask)
}
}
extension myToDoList {
func handleGetTasks(request: RouterRequest,
response: RouterResponse,
next: @escaping() -> Void) throws
{
Log.info("Show my tasks!")
response.status(.OK).send("Getting tasks!")
next()
}
func handleAddTask(request: RouterRequest,
response: RouterResponse,
next: @escaping() -> Void) throws
{
Log.info("Adding a task!")
response.status(.OK).send("Added a task!")
next()
}
}
main.swiftからこのクラスを呼び出して使います。
main.swift
import Kitura
import LoggerAPI
import HeliumLogger
import myToDoList
HeliumLogger.use()
let mytodolist = myToDoList()
Kitura.addHTTPServer(onPort: 8090, with: mytodolist.router)
Kitura.run()
だいぶスッキリしました。
Curlで呼び出してみる
$ curl -X GET localhost:8090/v1/tasks
Getting tasks!
$ curl -X POST localhost:8090/v1/tasks
Added a task!
データの配列を用意する
ローカルの配列にタスクを入れてToDoのやり取りをしてみます。
まずは、SwiftyJSONのインポートやtasksの配列の準備、初期値として一つタスクを要素として入れておきます。
myToDoList.swift
import Kitura
import LoggerAPI
import SwiftyJSON
public class myToDoList {
public let router = Router()
var tasks: [String] = []
public init() {
router.get("/v1/tasks", handler: handleGetTasks)
router.post("/v1/tasks", handler: handleAddTask)
tasks.append("牛乳を買う")
}
}
extension myToDoList {
func handleGetTasks(request: RouterRequest,
response: RouterResponse,
next: @escaping() -> Void) throws
{
Log.info("Show my tasks!")
response.status(.OK).send(json: JSON(tasks))
next()
}
func handleAddTask(request: RouterRequest,
response: RouterResponse,
next: @escaping() -> Void) throws
{
Log.info("Adding a task!")
response.status(.OK).send("Added a task!")
next()
}
}
この状態でGETを投げてみます。JSONで返ってくるはずです。
curl -X GET localhost:8090/v1/tasks
[
“牛乳を買う”
]
タスクが返ってきました。
データを配列に格納する
次にbodyparserのプラグインを実装します。
myToDoList.swift
・・・
public class myToDoList {
public let router = Router()
var tasks: [String] = []
public init() {
router.all("*", middleware: BodyParser())
router.get("/v1/tasks", handler: handleGetTasks)
router.post("/v1/tasks", handler: handleAddTask)
tasks.append("牛乳を買う")
}
}
・・・
レスポンスの処理を記載します。
myToDoList.swift
・・・
func handleAddTask(request: RouterRequest,
response: RouterResponse,
next: @escaping() -> Void) throws
{
guard let body = request.body else {
return
}
guard case let .json(json) = body else {
return
}
Log.info("Adding a task!")
・・・
ここで、返ってくるpayloadに対する処理をextentionを使って整理します。
extentionを使って以下のように書き直します。
RouterRequestExtension.swift
import Foundation
import Kitura
import SwiftyJSON
extension RouterRequest {
var json: JSON? {
guard let body = self.body else {
return nil
}
guard case let .json(json) = body else {
return nil
}
return json
}
}
myToDoListも変更します。ここではレスポンスがjsonで返って来なかった場合のエラー処理を施します。
myToDoList.swift
・・・
func handleAddTask(request: RouterRequest,
response: RouterResponse,
next: @escaping() -> Void) throws
{
guard let json = request.json else {
response.status(.badRequest)
next()
return
}
・・・
排他制御
get処理もadd処理も非同期で行われるので整合性を保つために排他制御を実装し同期処理にします。
Dispatchをimportし、queueを用意します。
myToDoList.swift
import Kitura
import LoggerAPI
import SwiftyJSON
import Dispatch
public class myToDoList {
public let router = Router()
var tasks: [String] = []
let queue = DispatchQueue(label: "com.expample.mytodo")
public init() {
router.all("*", middleware: BodyParser())
・・・・
AddTaskににJSONの同期処理を施します。
myToDoList.swift
・・・
let description = json["description"].stringValue
queue.sync {
tasks.append(description)
Log.info("Adding a task!")
response.status(.OK).send("Added a task!")
next()
}
処理の確認
まずはGETで確認
$ curl-X GET http://localhost:8090/v1/tasks
[
"牛乳を買う"
]
次にPOSTしてToDoを追加してみます。
$ curl -H "Content-type: application/json" -X POST -d '{"description":"郵便局に行く"}' http://localhost:8090/v1/tasks
Added a task!
GETして確認してみましょう。
$ curl-X GET http://localhost:8090/v1/tasks
[
"牛乳を買う",
"郵便局に行く"
]
いかがでしたでしょうか?WebAPIもSwitで書けるなんて、これからのSwitの発展に益々期待が持てちゃいます!
次回はDBとの連携やMobileとの連携について書いていきたいと思います。