Vapor でWebRTCを使ったビデオチャットを作成したまとめ

# はじめに

ビデオチャット機能を実装したいとの要望があり、 WebRTC について調べました。
WebRTC はとても面白く、魅力的な技術でしたが、同時にとても手強かったため、忘備録＆記憶の整理用この記事を書きました。

同じ頃に Swift製 WebFrameWork である Vapor に興味が沸き、「WebRTC のサーバーサイドを Vapor で書けば一度に両方の技術を取得できるのでは？」と思い、WebRTC 用のシグナリングサーバーを Vapor で作成しました。
作成した Swift 製のシグナリングサーバーはこちらです。

[https://simple-video-chat.work/](https://simple-video-chat.work/)

４文字以上の任意のルーム名を URL の後ろに入力し iOS 以外の異なるブラウザ同士でアクセスするとビデオチャットが始まります。

Ex: [https://simple-video-chat.work/xxxx](https://simple-video-chat.work/xxxxx)

勉強用に作成したため、いつまで残すかわかりません。ソースコードはこちらです。 

[O-Junpei/simple-video-chat-server](https://github.com/O-Junpei/simple-video-chat-server)


# ハンズオンで WebRTC の概要を理解する

@massie_g さんと @yusuke84 さんのハンズオンが素晴らしいのため、まず最初に取り掛かる事をお勧めします。
ブラウザ間でビデオチャットが表示されると感動します。

WebRTCハンズオン 概要編
https://qiita.com/massie_g/items/916694413353a3293f73

WebRTCハンズオン 準備編
https://qiita.com/massie_g/items/6141a1020dd8bdf6574e

WebRTCハンズオン 本編
https://qiita.com/yusuke84/items/43a20e3b6c78ae9a8f6c


# シグナリングサーバーを作成する

（上記ハンズオンに目を通した前提で進めます。）
WebRTCハンズオン本編 STEP3 で作られているようなシグナリングサーバーを Vapor で実装します。
シグナリングサーバーは通信相手の情報を交換する役割、ちょうどブラウザ同士の電話番号(SDPと呼ばれます)を交換するイメージです。
今回は同じルーム（URL）にアクセスしている相手にSDPを転送するシグナリングサーバーを作成しました。

``` swift:configure.swift
var websocketClients: [String: [WebSocket]] = [:]

// WebSockets
let wss = NIOWebSocketServer.default()
wss.get("socket", String.parameter) { ws, req in
    let room = try req.parameters.next(String.self)
    // roomがなければ初期化、既にある場合はcloseを削除
    if websocketClients[room] == nil {
        websocketClients[room] = []
    } else {
        websocketClients[room] = websocketClients[room]!.filter({
            !$0.isClosed
        })
    }
    websocketClients[room]!.append(ws)
    ws.onText { ws, text in
        for client in websocketClients[room]! {
            if client.isClosed {
                return
            }
            if ws === client {
                print("slip sender")
            } else {
                // roomが一緒
                client.send(text)
            }
        }
    }
}
services.register(wss, as: WebSocketServer.self)
```

参考: [Vapod Docs Using WebSockets](https://github.com/O-Junpei/simple-video-chat-server)
参考: [VaporでWebSocketを使ったチャットアプリを作る](https://qiita.com/takoikatakotako/items/543458823e67dfdde881)


# HTML ページの生成

シグナリングサーバーができたので、次はフロント（HTML）をのレンダリング機能を作成します。
`routes.swift`は Rails における `config/routes.rb` で、ルーティングの設定を行うことができます。
４文字以上のルーム名が入力された場合はビデオチャットページ `index.html` へ、それ以外は Vapor のデフォルトページ `welcome.heml` をレンダリングしています。
`index.html`の中身(JS)はハンズオンからお借りしました。


``` swift:routes.swift
import Vapor

/// Register your application's routes here.
public func routes(_ router: Router) throws {
    // Top
    router.get { req in
        return try req.view().render("welcome")
    }
 
    // room
    router.get("/", String.parameter) { req -> Future<View> in
        let room = try req.parameters.next(String.self)
        if room.count < 4 {
            return try req.view().render("welcome")
        }
        return try req.view().render("index")
    }
}
```

参考: [Getting Started | Leaf](https://docs.vapor.codes/3.0/leaf/getting-started/)


# デプロイ

作成した Vapor のアプリケーションを Docker化し、GCPのCompute Engin にデプロイしました。
以下の構成になっています。

![Untitled Diagram.png](https://qiita-image-store.s3.amazonaws.com/0/63855/0e0ce135-c94e-fbfc-915d-20e713abbbcb.png)

以下のコマンドでローカルでも実行できます。

```bash
$ docker run -d -p 80:80 kabigon/simple-video-chat:latest
```

# 完成

Android のChorme と PC の Chorme で繋げています。
<img width="1919" alt="スクリーンショット 2019-03-12 9.49.17.png" src="https://qiita-image-store.s3.amazonaws.com/0/63855/8218e1d9-a4e5-d96f-aed6-b30bb4682ed1.png">


# 早く知りたかったこと、詰まったこと

## シグナリングサーバー(WebSocket)のデバックがしたい
[wsta](https://github.com/esphen/wsta) は シンプルな WebSocket クライアントです。
HomeBrew で簡単にインストールすることができ、シグナリングサーバーのデバッグに役立ちます。

```bash
$ brew tap esphen/wsta https://github.com/esphen/wsta.git
$ brew install wsta
```

ターミナルを2つ開き、WebSocket で接続する。
片方に入力した文字が転送される。

```bash
$ wsta wss://simple-video-chat.work/socket/xxxx // or
$ wsta ws://localhost:8080/socket/xxxx
```

## WebRTC は localhost 以外はSSL 必須

WebRTC を使うには SSLが必要です。
オンラインで使う場合はlet's encrypt などでSSL化してあげましょう。


## WebRTC のデバッグをしたい

`chrome://webrtc-internals/` と打ち込むとWebTRTC のデバッグができます。
参考: [WebRTCデバッグ入門](https://qiita.com/yusuke84/items/8d232c8d24156f16e8ba)


## NginxによるリバースプロキシとWebSocketを同時に使用する時は設定が必要

>WebSocketのハンドシェイク時に使われる Upgrade ヘッダと Connection ヘッダ は Hop-by-Hop ヘッダ である。 通常のEnd-to-End headerと異なり基本的に一度の転送に対して有効なヘッダなため、ReverseProxyを間に挟んでいる場合これらのheaderはバックエンドサーバまで届かない。

Nginxによるリバースプロキシ + WebSocket を使う時は注意が必要です。
nginx.conf ファイルに以下を追記します。

```
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
```

参考: [Nginxを用いたWebSocketサーバのReverseProxy構成及びSSL/TLS接続](http://uorat.hatenablog.com/entry/2016/09/19/203939)


## iOS のブラウザ(Safari & Chorme)では WebRTCは使えない場合がある

制限が多く、思ったように動かない場合が多いため注意が必要です。
(追記: Safari 12.1 で映像コーデック VP8 に対応することで、 WebRTC は主要ブラウザで問題なく利用することができるようになる。とのことです。)

参考: [Safari と WebRTC について](https://gist.github.com/voluntas/af937c1fd353e6f677e155b53d661807)
参考: [WebRTC の今](https://gist.github.com/voluntas/4890458f4f9191660ab659d37ff67a83)



# iOS アプリでWebRTC を使ったビデオチャットアプリを作りたい

iOS アプリのブラウザ(WebView)では制限が多いため、WebRTC に対応したブラウザを CocoaPods などでインストールする必要があります。
以下の記事がとても良かったのでおすすめです。

SwiftでWebRTC実装ハンズオン 事前準備編
https://qiita.com/tnoho/items/3b94371e59fe8ad6ce03

SwiftでWebRTC実装ハンズオン 本編
https://qiita.com/tnoho/items/f5afa3ba749eed9b9716

ただ、上記記事のソースコードは Swift3 で書かれており、少し古くなってしまっているので、Swift4.2 で書き直しました。
近いうちにこの記事の続きとして公開します。


# やっぱり辛い逃げよう

WebRTCを自前で実装すると大変です。

- Skyway
https://nttcom.github.io/skyway/

- 時雨堂
https://shiguredo.jp/