1週間ほど、空いた時間にKituraというかサーバサイドSwiftを触ってみて、PostgreSQLと連携してデータをinsertしたりselectしたりできるようになったので、メモ。
目次
Fluent
Swiftでデータベースを扱うのにいいライブラリないかな?と探していて見つけたのが、このFluentです。
ただ、この中のSQLクラスがテーブル名などをバッククォートで囲んでしまうため、そのままではPostgreSQLでは使えませんでした・・。
ということで、forkしてクォート文字を設定できるようにしてみました。
https://github.com/d-abe/fluent
同様に、fluentのPostgreSQLドライバも合わせてforkしています。
https://github.com/d-abe/fluent-postgresql
Swift
今回は、Swiftの2/25版を使う前提として考えています。
これを使うと、swift buildに --fetch (Package.swift記載のパッケージ取得のみを行う)とか -Xswiftc (これを使うとswiftcにパラメータを受け渡せるようになり、Kituraを使ったというかCの混ざったプロジェクトでエラーが出なくなる)とかが追加されています。
ファイル構成
今回作ったファイルは以下のような構成になっています。
├── Database │ └── sample.sql ├── Makefile ├── Package.swift └── Sources └── KituraSample ├── Common │ └── Constants.swift ├── Controllers │ └── ArticleController.swift ├── Filters │ ├── FluentFilter.swift │ └── HeaderFilter.swift ├── Models │ ├── Article.swift │ └── ModelExtension.swift ├── Routes │ └── MyRouter.swift └── main.swift
これは以下のリポジトリに一式置いています。
https://github.com/d-abe/kitura_sample
ファイル名 | 説明 |
---|---|
Database/sample.sql | テーブル定義のSQLファイル |
Makefile | Makefile |
Package.swift | Swift Package Managerのパッケージ定義のソース |
Common/Constants.swift | 共通定数クラス |
Controllers/ArticleController.swift | Article関連コントローラクラス |
Filters/FluentFilter.swift | fluent初期化(データベース接続)フィルタ |
Filters/HeaderFilter.swift | レスポンスヘッダ設定フィルタ |
Models/Article.swift | Articleモデルクラス |
Models/ModelExtension.swift | ModelにNSDateやIntのstring相互変換メソッドを追加 |
Routes/MyRouter.swift | ルーティング定義クラス |
main.swift | エントリポイント |
では、それぞれについて見ていきます。
エントリポイント
プログラムを実行すると呼び出されるのがこのmain.swiftになります。
ルーティングクラスをインスタンス化してHttpServerに渡して(ポート8090で)起動させているだけですね。
この中のLithiumLoggerというのはHeliumLoggerをログレベルの設定ができるようにしたもので、ここではそのレベルを設定しています。
ルーティング
ルーティングのMyRouterクラスは以下の通りです。
これを見ると、useでミドルウェアを渡せるのがわかりますが、その前にURLのパターンを設定できるので全体に対してもできるし/member内だけとかグルーピングして設定することもできます。
これによって、共通のDB接続とかは全体、認証関連は /member以下、のようなことができます。
ミドルウェア適用の順序が保証されるかどうかは不明なので、順序に依存しないような構造にすべきですね。
あとは、GETの時はArticleController.getAll()、POSTの時はArticleController.add()を呼び出すようにしています。
コントローラとモデル
では、コントローラクラスでは何をしているのか見てみます。
まず、
let json = JSON(Article.findAll().toJSON())
ここで、Article.findAll()を実行してデータベースから全件取得して結果をJSONに変換しています。
Articleクラス
モデルクラスのArticleを見ておきましょう。
大したことはしていません。プロパティと [String:String] の相互変換と、staticメソッドのfindAll()を定義しているだけです。
モデルに関しては、
- プロパティの定義
- serialize/initの定義
- staticメソッドの実装
をどんどん行う感じになるかと思います。
ArticleController.add()
コントローラに戻って、add()メソッドを見てみます。
if let body = request.body { if let json = body.asJson() {
この部分ですが、ルータ内で設定していたBodyParserというミドルウェアが、Content-Typeの値によって自動的にrequest.bodyに値がセットして、(今回はapplication/jsonのContent-Typeで渡す前提なので)body.asJson()を使ってJSONの値を取得しています。
データのINSERT
INSERTも簡単です。
let article = Article(comment: comment, createdAt: NSDate()) article.save()
Articleに値をセットし、save()を呼ぶだけです。
JSONの返却
コードを見たまんまですが、
response.status(HttpStatusCode.OK).sendJson(json)
これで、HTTPステータスコードとJSONを返却できます。
サンプルの動かし方
リポジトリのREADMEに書いておいたので、そちらを見てくださいw
https://github.com/d-abe/kitura_sample/blob/master/README.md
Macなら、Swiftと依存ライブラリが入れてあれば、makeして.build/debug/KituraSampleを実行するだけでいけると思います。
おわりに
実際には入力のバリデーションだったり、結果のPaginationだったりと色々やらないといけないことはたくさんあります。
その辺りに関しても便利なライブラリがあれば紹介していきたいです。
また、fluentはSQLインジェクションの脆弱性がありそう(SQL構築の際に文字列をそのまま結合している)なので、プレースホルダを作って扱うように修正した方が良さそうです。
これについては、Pull Requestがすでに上がっていたので、そのうちマージされるかもしれません。