Server Side Swift(v4.0.3)でLINE BOTの作成 (とハマったところ） #love_swift


**結構ハマった**んですけど、エコーボット作れたので記録します。

> [Swift愛好会合宿 2018/1/20-1/21](https://love-swift.connpass.com/event/72804/)で記事書いてます。 #love_swift

やっぱり言語勉強にBOTはいい題材な気がします。

## 環境構築

こっちにまとめました。

* [Ubuntu 16.04にSwift4をインストールしてVaporでハローワールドするまで #love_swift](https://qiita.com/n0bisuke/items/f62e8f425fc20ede6ffc)
* [Swift4でのPackage.swiftエラー #love_swift](https://qiita.com/n0bisuke/items/605c71a803eb8c6863f5)

バージョンなどは

* Ubuntu 16.04
* Swift 4.0.3
* Vapor 2.4.0

となっています。

## Package.swiftの準備

```Package.swift
// swift-tools-version:4.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "webapp",
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        .package(url: "https://github.com/vapor/vapor.git", from: "2.4.0")
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages which this package depends on.
        .target(
            name: "webapp",
            dependencies: ["Vapor"]),
    ]
)
```

## main.swiftの編集

```main.swift
import Vapor

let drop = try Droplet()
let endpoint = "https://api.line.me/v2/bot/message/reply"
let accessToken = ""

drop.get("hello") { req in
    print(req)
    return "Hello Vapor!!!"
}

drop.post("callback"){ req in
    print(req);

    guard let object = req.data["events"]?.array?.first?.object else{
        return Response(status: .ok, body: "this message is not supported")
    }
    
    guard let message = object["message"]?.object?["text"]?.string, let replyToken = object["replyToken"]?.string else{
        return Response(status: .ok, body: "this message is not supported")
    }
    
    print("-----------------");
    print(message);
        
    var requestData: JSON = JSON()
    try requestData.set("replyToken", replyToken)
    try requestData.set("messages", [
        ["type": "text", "text": message]
    ])
    
    let response: Response = try drop.client.post(
        endpoint,
        query: ["name": "mybot"],
        [
            "Content-Type": "application/json",
            "Authorization": "Bearer \(accessToken)"
        ],
        requestData
    )

    print(response)
    return Response(status: .ok, body: "reply")
}

try drop.run()
```

## 実行

```
$ swift build
$ ./.build/x86_64-unknown-linux/debug/webapp
```

これでおうむ返しのLINE BOTができました。

![](https://i.gyazo.com/7af666444ee5cbdf1d474a8bfda9cc4f.png)

この辺をみると良さそう

## ハマったポイントはHTTP Request

基本的には[Vapor + LINE Messaging API で Bot を開発する](https://qiita.com/rb-de0/items/7317dc0e2b90627c49d5)を参考にしながら書いたのですが、

```
あとはこのリクエストをURLSessionに投げれば・・・と思ったのですが、UbuntuでURLSessionを使うと何故か正しく機能しないので（corelib-foundationのバグですかね？）、実際にはcurlをTask(Process）を使って叩くようにしています。Dropletのclientを使ってPOSTしてもダメだったのでよく分からない。
```

と参考記事にも書いてあった通り、`URLRequest(url: URL(string: endpoint)!)`が動いてくれず、VaporのHTTP Clientの`drop.client.post()`なども調べたのですが、[ドキュメント](https://docs.vapor.codes/2.0/http/client/)に書いてある記述だとエラーになり、Header情報を付加しないでのリクエストだと問題ないけどLINE BOTの場合はAuthorizationヘッダーにシークレット情報を詰め込む必要があったりと、やり方を検討するところでかなり時間がかかりました汗

ドキュメントに書いてある

```
try drop.client.get("http://some-endpoint/json", headers: [
    "API-Key": "vapor123"
])
```

の記述はどうやらもう使えない(?)みたいで、 [Server-Side-Swift VaporでAPIを作って学んだことまとめ](https://qiita.com/hirothings/items/a313b1cc02e06d33938f#http)に書いてあるHTTPリクエストのやり方だとコンパイルエラーが出なかったです。

```
let response: Response = try drop.client.get("https://qiita.com/api/v2/items", query: [
    "page": 1,
    "per_page": 100,
    "query": "user:hirothings"
], [
    "Authorization": "Bearer hogehogehoge"
])
```

また、全くドキュメントに書いてなかったですが、Vaporでの実装例をGitHubで探して

> https://github.com/FabrizioBrancati/SwiftyBot/blob/master/Sources/SwiftyBot/main.swift#L403

こちらのソースに行き着きました。どうやらPOSTリクエストのBODYは一番最後の引数になるようです。

ドキュメントでは`drop.client.get(エンドポイント,ヘッダー)`といった呼び出し方でしたが、
実際は **`drop.client.get(クエリ,ヘッダー,ボディ)`**という指定の仕方でした。

VaporのHTTP Clientの仕様が変わってるようでしたがドキュメントにも記載がないので大元のコードを探してたけどたどり着けず... 隣にいた@jollyjoesterさんに助けてもらい（ありがとうございました！）

> https://github.com/vapor/engine/blob/master/Sources/HTTP/Request/Request.swift#L12-L17

にたどり着き確認できました。

```Request.swift
    public init(
        method: Method,
        uri: URI,
        version: Version = Version(major: 1, minor: 1),
        headers: [HeaderKey: String] = [:],
        body: Body = .data([])
    )
```

ログを見てたら`2.0.0-bata.3`以降はこの仕様になっているようです。
ドキュメントが追いついてない可能性がたかそうですね..


ということで`drop.client.post()`は`endpoint,query,header,body`の順番で引数を与えることで利用できます。

```
try drop.client.post(
        endpoint,
        query: ["name": "mybot"],
        [
            "Content-Type": "application/json",
            "Authorization": "Bearer \(accessToken)"
        ],
        responseData
    )
```

何はともあれ、これで最新バージョンのServer Side SwiftでLINE BOTを作ることができました！

まだまだServer Side Swiftは調べてても日本語記事が全然出てこなく、発展途中感がハンパないですが興味のある人がいたら参考にしてみてもらえたら幸いです。