【Swift】サーバーサイドSwift 〜Vapor 始めの第一歩〜【Vapor】

# **追記（2016/07/13）**
この記事を投稿した２日後にドキュメントが新しいバージョンになっていたため、新しく書き直しました。
書き直した記事は以下です。
[【Swift】サーバーサイドSwift 〜Vapor 始めの第一歩〜【Vapor v0.13】](http://qiita.com/taji-taji/items/9dacd7d21b2ad19fc420)

---

サーバーサイドSwiftのフレームワークの一つである **Vapor** をはじめてみようと思うので、手順を書き残そうと思います。

順調にできるはずの流れを一通り書き、はまった点を最後にまとめていきます。

使ってみた印象としては、やはりタイミングがタイミングなので、Swiftのバージョンなどちょっとした違いで簡単にエラーが出てつまづいてしまいますが、
そのあたりがなければ非常に簡単に試すことができるなと思いました。

# 環境

- OS X: 10.11.5（15F34）
- Xcode: Version 7.3.1 (7D1014)
- Toolchain: Swift Development Snapshot 2016-06-06 (a)


# ドキュメント
https://vapor.readme.io/v0.12
今回はこちらの公式ドキュメントの手順に従って進めていきます。

# Swift3のインストール

ドキュメントには、Swfit3のインストール方法が、3種類書かれています。

1. macOSへのインストール
- Ubuntuへのインストール
- Swiftenvを使用したインストール

macOSの方は1か3、
Ubuntuの方は2か3
の方法でインストールします。

ここでは、macOSでSwiftenvを使ったインストール方法を記載します。

## Swiftenvからインストール

Swiftのバージョン切り替えツールをインストールします。（RubyのrbenvとかPythonのpyenvみたいなやつ）

### Swiftenvをインストール

```shell-session
# Homebrewからインストール
brew install kylef/formulae/swiftenv

# パスを通す
echo 'if which swiftenv > /dev/null; then eval "$(swiftenv init -)"; fi' >> ~/.bash_profile
source ~/.bash_profile
```

### Swift3のインストール

```shell-session
# インストール
swiftenv install DEVELOPMENT-SNAPSHOT-2016-06-06-a
# インストールしたバージョンをグローバルに設定
swiftenv global DEVELOPMENT-SNAPSHOT-2016-06-06-a
```

### 確認

```shell-session
swift --version

> Apple Swift version 3.0-dev (LLVM cb08d1dbbd, Clang 383859a9c4, Swift 9e8266aaeb)
```

**3.0-dev** になっていればOK


### インストールされている全てのSwiftのバージョンを確認

```shell-session
swiftenv versions
```

詳しい使い方は[こちら](https://github.com/kylef/swiftenv)をご覧ください。


# CLIのインストール

Vaporのコマンドラインツールをインストールします。

### インストール

```shell-session
curl -L cli.qutheory.io -o vapor
chmod +x vapor
sudo mv vapor /usr/local/bin
```

### 確認

```shell-session
vapor help
```

ここで、vaporコマンドが使用できて、以下のようなヘルプ内容が出ればOKです。

```shell-session
Usage: /usr/local/bin/vapor [version|help|clean|build|run|new|self|xcode|heroku|docker]

Available Commands:

  version:
      display Vapor CLI version


  help:

  clean:

  build:
      build <module-name>
      Builds source files and links Vapor libs.
      Defaults to App/ folder structure.


  run:
      runs executable built by vapor build.
      use --release for release configuration.


  new:
      new <project-name>
      Clones the Vapor Example to a given
      folder name and initializes an empty
      Git repository inside it.


  self:
      install:
          Installs the CLI to the /usr/local/bin

      update:
          Downloads the latest version of
          the Vapor command line interface
          and compiles it into a binary.

      compile:
          Compiles and caches the CLI
          to improve performance.

  xcode:
      Generates and opens an Xcode Project.


  heroku:
      init:
          Configures a new heroku project

  docker:
      Setup and run vapor app via docker
      sub commands: init|build|run|enter

      init:
          Creates a Dockerfile

      build:
          Build the docker image, using the swift
          version specified in .swift-version.

      run:
          Run the app in a docker container with the
          image created by running 'docker build'

      enter:
          Enter the docker container (useful for
          debugging purposes)


Community:
    Join our Slack if you have questions,
    need help, or want to contribute.
    http://slack.qutheory.io
```

### CLIの高速化

一度CLI自身をコンパイルすることで、キャッシュが作られ高速に動作するようになるようです（おそらく）

```shell-session
vapor self compile
```

### CLIのアップデート

CLI自身をアップデートするときは次のようにします。

```shell-session
vapor self update
```

# プロジェクトを作成

```shell-session
vapor new hello-world
```

このコマンドで色々とプロジェクトファイルをセットアップしてくれます。

セットアップが完了すると、オシャレなVaporのマークが出てきました。

![スクリーンショット 2016-07-09 16.44.28.png](https://qiita-image-store.s3.amazonaws.com/0/11914/9ec31dd1-0a12-2201-8ac2-fc4303e05060.png)

（↑私は`vapor new hello-world-vapor`としたので、`Project "hello-world-vapor"`となっています。）

また、セットアップ完了と同時にXcodeでプロジェクトが自動的に立ち上がりました。

![スクリーンショット 2016-07-09 16.47.45.png](https://qiita-image-store.s3.amazonaws.com/0/11914/52d89270-936e-2bcd-f5c1-6cd9d3720dc0.png)

### ディレクトリ構成

とりあえず、２階層くらいまでのディレクトリ構成を表示してみます。

```shell-session
.
├── App
│   ├── Controllers
│   ├── Middleware
│   ├── Models
│   └── main.swift
├── Config
│   ├── app.json
│   ├── development
│   ├── secrets
│   └── servers.json
├── Localization
│   ├── default.json
│   ├── en.json
│   └── es.json
├── Package.swift
├── Packages
│   ├── C7-0.9.1
│   ├── CryptoEssentials-0.8.0
│   ├── Fluent-0.7.0
│   ├── HMAC-0.8.0
│   ├── Jay-0.12.0
│   ├── Mustache-0.10.0
│   ├── PathIndexable-0.2.0
│   ├── Polymorphic-0.2.0
│   ├── S4-0.10.1
│   ├── SHA1-0.8.0
│   ├── SHA2-0.8.0
│   ├── Socks-0.8.1
│   ├── Strand-1.5.1
│   ├── Vapor-0.13.0
│   ├── VaporMustache-0.9.0
│   └── libc-0.1.0
├── Procfile
├── Public
│   ├── images
│   └── styles
├── README.md
├── Resources
│   └── Views
├── VaporApp.xcodeproj
│   ├── C7_Info.plist
│   ├── Configs
│   ├── CryptoEssentials_Info.plist
│   ├── Fluent_Info.plist
│   ├── HMAC_Info.plist
│   ├── Jay_Info.plist
│   ├── Mustache_Info.plist
│   ├── PathIndexable_Info.plist
│   ├── Polymorphic_Info.plist
│   ├── S4_Info.plist
│   ├── SHA1_Info.plist
│   ├── SHA2_Info.plist
│   ├── SocksCore_Info.plist
│   ├── Socks_Info.plist
│   ├── Strand_Info.plist
│   ├── VaporMustache_Info.plist
│   ├── Vapor_Info.plist
│   ├── libc_Info.plist
│   ├── project.pbxproj
│   ├── project.xcworkspace
│   ├── xcshareddata
│   └── xcuserdata
└── app.json
```

APPの中にControllersやModels、Resourcesの中にViewsがありますね。
そして、何やら中核になりそうな`main.swift`というファイルがAPPの中にあります。

この`main.swift`に色々書いていきます。

# `main.swift`の編集

デフォルトで色々書いていますが、以下のように書き換えます。

```swift:main.swift
import Vapor
import VaporMustache

let app = Application()

app.get("hello") { request in
    return "Hello, world"
}

app.start()

```

また、同じくデフォルトでできている以下のファイルを削除します。

- App/Controllers/UserController.swift
- App/Models/User.swift
- App/Middleware/SampleMiddleware.swift


# コンパイル

```shell-session
vapor build
```

`vapor build`コマンドは、中で`swift build`を実行しているようです。

# サーバー起動

ビルドが完了したらサーバーを起動させます。

```shell-session
vapor run
```

このコマンドでウェブサーバーが立ち上がります。

```shell-session
$ vapor run
Running...
No command supplied, defaulting to 'serve'.
Server starting at 0.0.0.0:8080
```

また、ポートを指定することもできます。

```shell-session
vapor run --port=8080
```

これで、ブラウザから`http://localhost:8080`へアクセスすると、今立ち上げたサーバーにアクセスすることができます。

ただ、ルートパスへは何も出力していないので、真っ白のページになっているかと思います。

先ほど、`main.swift`で処理を記入したのは`hello`というパスだったので、`http://localhost:8080/hello`にアクセスします。

すると、「Hello, world」と画面上に表示されるかと思います。

![スクリーンショット 2016-07-10 18.58.26.png](https://qiita-image-store.s3.amazonaws.com/0/11914/453fe777-ee23-f73e-bd5a-bb77da08dffc.png)


このようにできれば完了です。

以上、始めの第一歩として触ってみました。

# はまったところ

スムーズに行けばものすごく簡単なはずなのですが、いくつかハマりまくったところがありました。

## SwiftenvでのSwift3.0-Devのインストール

私の場合ですが、Swiftenvの正しい使い方がわかっていなかったこともあり、macOSへのSwift3のインストールを行って、その後Swiftenvをインストールしてその後の設定をきちんとしていなかったりと、、

そのあたりの手順がバラバラになっていたためか、目的の`DEVELOPMENT-SNAPSHOT-2016-06-06-a`がきちんとインストールされていませんでした。

エラーが出た場合は、Swiftのバージョンが正しいかを確認した方がいいかもしれません。


## `Package.swift`で設定するVaporのバージョン

`vapor new`コマンドで作成されるデフォルトの`Package.swift`では、以下のように、vapor.gitのマイナーバージョンの指定が**13**、vapor-mustache.gitのマイナーバージョンが**9**となっています。

```swift:Package.swift
import PackageDescription

let package = Package(
    name: "VaporApp",
    dependencies: [
        .Package(url: "https://github.com/qutheory/vapor.git", majorVersion: 0, minor: 13),
        .Package(url: "https://github.com/qutheory/vapor-mustache.git", majorVersion: 0, minor: 9)
    ],
    exclude: [
        "Config",
        "Database",
        "Localization",
        "Public",
        "Resources",
        "Tests",
    ]
)
```

こちらだとなぜかエラーが出てしまいました。

```shell-session
$ vapor build
Cloning https://github.com/qutheory/vapor.git
HEAD is now at fc05b8b update environment description
Resolved version: 0.13.0
Cloning https://github.com/open-swift/S4.git
HEAD is now at 84e6ac0 add responder representable (#72)
Resolved version: 0.10.1
Cloning https://github.com/open-swift/C7.git
HEAD is now at 6660400 add StructuredDataFallibleRepresentable (#48)
Resolved version: 0.9.1
Cloning https://github.com/czechboy0/Jay.git
HEAD is now at 6ee6278 Updating to swift 3 preview 1 (#28)
Resolved version: 0.12.0
Cloning https://github.com/CryptoKitten/HMAC.git
HEAD is now at 63eac3a Merge pull request #5 from CryptoKitten/LoganWright-patch-1
Resolved version: 0.8.0
Cloning https://github.com/CryptoKitten/CryptoEssentials.git
HEAD is now at 058196b Merge pull request #8 from CryptoKitten/update
Resolved version: 0.8.0
Cloning https://github.com/CryptoKitten/SHA2.git
HEAD is now at e090a0c Merge pull request #3 from CryptoKitten/LoganWright-patch-1
Resolved version: 0.8.0
Cloning https://github.com/CryptoKitten/SHA1.git
HEAD is now at 869025c Merge pull request #3 from CryptoKitten/update-06-20
Resolved version: 0.8.0
Cloning https://github.com/qutheory/fluent.git
HEAD is now at 0a15ab2 Merge pull request #48 from qutheory/schema
Resolved version: 0.7.0
Cloning https://github.com/qutheory/polymorphic.git
HEAD is now at 734b45d Merge pull request #1 from qutheory/open-swift-9
Resolved version: 0.2.0
Cloning https://github.com/qutheory/path-indexable.git
HEAD is now at a58a785 Merge pull request #2 from qutheory/variable-rename
Resolved version: 0.2.0
Cloning https://github.com/ketzusaka/Strand.git
HEAD is now at 043d34d Fix linux compiling
Resolved version: 1.5.1
Cloning https://github.com/czechboy0/Socks.git
HEAD is now at ab383f6 Improved connect with timeout (#58)
Resolved version: 0.8.1
Cloning https://github.com/qutheory/libc.git
HEAD is now at ffd692c add readme
Resolved version: 0.1.0
Cloning https://github.com/qutheory/vapor-mustache.git
HEAD is now at c9f07b7 vapor 13
Resolved version: 0.9.0
Cloning https://github.com/Zewo/Mustache.git
HEAD is now at abe2dc8 Travis Supports Xcode8
Resolved version: 0.10.0
2016-07-10 18:36:37.058 xcodebuild[22979:616679] [MT] PluginLoading: Required plug-in compatibility UUID ACA8656B-FEA8-4B6D-8E4A-93F4C95C362C for plug-in at path '~/Library/Application Support/Developer/Shared/Xcode/Plug-ins/RealmBrowser.xcplugin' not present in DVTPlugInCompatibilityUUIDs
Compile Swift Module 'C7' (23 sources)
Compile Swift Module 'CryptoEssentials' (15 sources)
Compile Swift Module 'PathIndexable' (2 sources)
Compile Swift Module 'Strand' (1 sources)
Compile Swift Module 'SocksCore' (14 sources)
Compile Swift Module 'libc' (1 sources)
Compile Swift Module 'Mustache' (29 sources)
/Users/tajika/hello-world-vapor/Packages/Strand-1.5.1/Sources/Strand.swift:52:48: error: cannot convert value of type '(arg: UnsafeMutablePointer<Void>) -> UnsafeMutablePointer<Void>?' to expected argument type '(@convention(c) (UnsafeMutablePointer<Void>?) -> UnsafeMutablePointer<Void>?)!'
                guard pthread_create(&pt, nil, runner, pointer) == 0 && pt != nil else {
                                               ^~~~~~
/Users/tajika/hello-world-vapor/Packages/CryptoEssentials-0.8.0/Sources/ArrayProtocol.swift:131:73: error: extraneous argument label 'options:' in call
                    return NSData(bytes: bytesArray).base64EncodedString(options: [])
                                                                        ^~~~~~~~~~
                                                                         
/Users/tajika/hello-world-vapor/Packages/CryptoEssentials-0.8.0/Sources/NSData+Extensions.swift:35:48: error: extraneous argument label 'options:' in call
                return self.base64EncodedString(options: .lineLength64Characters)
                                               ^~~~~~~~~~
                                                
<unknown>:0: error: build had 2 command failures
error: exit(1): /Library/Developer/Toolchains/swift-DEVELOPMENT-SNAPSHOT-2016-06-06-a.xctoolchain/usr/bin/swift-build-tool -f /Users/tajika/hello-world-vapor/.build/debug.yaml

Need help getting your project to build?
Join our Slack where hundreds of contributors
are waiting to help: http://slack.qutheory.io

Error: Could not build project.
Note: Make sure you are using Swift 3.0 Snapshot 06-06
tajika@asakalab-no-MacBook-Air:~/hello-world-vapor$ vim .
tajika@asakalab-no-MacBook-Air:~/hello-world-vapor$ vapor build
error: The dependency graph could not be satisfied because an update to `https://github.com/qutheory/vapor.git' is required

Error: Could not fetch dependencies.
Note: Make sure you are using Swift 3.0 Snapshot 06-06
```

ということで、
下のようにvapor.gitのマイナーバージョンを**11**、vapor-mustache.gitのマイナーバージョンを**7**とすることでbuildができました。

```swift:Package.swift
import PackageDescription

let package = Package(
    name: "VaporApp",
    dependencies: [
        .Package(url: "https://github.com/qutheory/vapor.git", majorVersion: 0, minor: 11),
        .Package(url: "https://github.com/qutheory/vapor-mustache.git", majorVersion: 0, minor: 7)
    ],
    exclude: [
        "Config",
        "Database",
        "Localization",
        "Public",
        "Resources",
        "Tests",
    ]
)
```

これが解決されるまでにXcode8-betaをインストールしてみたり、Xcodeを再インストールしてみたり、Swiftの違うバージョンを試してみたりと色々と試行錯誤してかなりの時間を費やしてしまいました。