Vue CLI3のDevServerでWebSocketを使う

# はじめに

Vue CLI 3 で作成したプロジェクトにてDevServer経由でWebSocket通信を行う方法について。Vue CLI 3で作成したプロジェクトを開発サーバで動作させる場合にはリクエストのプロキシ機能がある。
開発中はこれを使うことで、APIへのリクエストをリダイレクトしてくれる。
ただ、WebSocketで接続する場合に若干ハマったので、その内容について記事にします。  

※記事を書く際に動作確認をしていたら、ブラウザのキャッシュなのか何なのか、エラーが出たり出なかったりしたので、不要な設定や間違った設定があるかもしれないです。詳しい人がいたら教えてください。。。

# 結論

* `vue.config.js`に以下の設定を追加することで、`http://localhost:4000/websocket`のエンドポイントに対してプロキシが貼られる。
* この状態で``new WebSocket(`ws://${location.host}/websocket`);``とすることで、`localhost:4000/websocket`と``WebSocket通信が行える

[devServer.proxy](https://cli.vuejs.org/config/#devserver-proxy)

```vue.config.js
module.exports = {
    devServer: {
        proxy: {
            '/websocket': {
                target: 'http://localhost:4000',
                ws: true,
                changeOrigin: true
            }
        }
    }
}
```



# サンプルプロジェクト

以下でサンプルのWebSocketプロジェクトを作成して確認した。
ソースコードは以下にアップしている。とは言うものの設定ファイルのみで終わるので、参考程度に

https://github.com/dbgso/vuecliwebsocket

開発環境は以下の通り

* Ubuntu16.04
* Python3.5.2
* Vue CLI 3.2.2
* Node.js 10.11.0
* yarn 1.12.3

WebSocketサーバは何でも良かったが、粗結合な環境での確認の意味もあったので、Node.js以外ということでPythonの`tornado`を用いて作成した。

以降、APIサーバを4000ポート、フロント用のdevServerが8080ポートで起動した場合の例とする。


# 開発サーバのプロキシ設定

Vue CLI3で作成したプロジェクトで、`yarn serve`や`npm run serve`とするとデフォルト8080ポートで開発サーバが起動する。  
この状態で`fetch('/api/v1/get')`のようにリクエストを投げると`localhost:8080/api/v1/get`に送られる。
しかし、8080で立っているのは開発サーバのみなので、当然`/api/v1/get`は存在しない。
`fetch('http://localhost:4000/api/v1/get')`のように絶対パスで指定しても良いが、CORSに引っかかるし開発と本番で場合分けする必要があるので非推奨。

そこで、Vue Cli3の開発サーバでは、APIへのリクエストに対してのリダイレクト機能を用いる。
その他の設定も含め、詳細については [devServer.proxy](https://cli.vuejs.org/config/#devserver-proxy) 参照。


```vue.config.js
module.exports = {
  devServer: {
    proxy: 'http://localhost:4000'
  }
}
```

例えば↑のように`vue.config.js`に設定しておくと、以下のコードを実行した場合に`http://localhost:4000/api/v1/get`にリクエストが送信される。
4000ポートでAPIサーバを立てておけば、実際の状態に近い状態で開発できます。

# WebSocketのプロキシ設定

通常のHTTPリクエストの場合は↑の方法で大体OKなんですが、WebSocket通信を行う場合は若干ハマりました。
と言っても、変更する部分は設定ファイルのみなので、最初に書いておきます。

```vue.config.js
module.exports = {
    devServer: {
        proxy: {
            '/websocket': {
                target: 'http://localhost:4000',
                ws: true,
                changeOrigin: true
            }
        }
    }
}
```

基本的には`ws, changeOrigin`を設定することです。
ただし、ハマりどころは`/websocket`で明示的にパスを指定することでした。

最初は以下のように設定したんですが` ERROR  ValidationError: webpack Dev Server Invalid Options`というエラーが出ました。
`ws`等はパスごとに設定する項目なので項目が違いますよということでした。

```vue.config.js
module.exports = {
    devServer: {
        proxy: 'http://localhost:4000',
        ws: true,
        changeOrigin: true
    }
}
```

次に、以下のようにパスを`/`にすると開発サーバに対して影響を与えてしまったので、これも以下のようにエラーが出ました。

`WebSocket connection to 'ws://192.168.10.104:8081/sockjs-node/326/bsdw0vzs/websocket' failed: Invalid frame header`

```vue.config.js
module.exports = {
    devServer: {
        proxy: {
            '/': {
                target: 'http://localhost:8080',
                ws: true,
                changeOrigin: true
            }
        }
    }
}
```

開発サーバ自体もWebSocket通信を行っているため、ルートを指定してしまうとその通信自体もリダイレクトしてしまってエラーが出続けてしまいました。
WebSocketで通信する場合は必要なパスのみに設定が必要みたいです。


# 備考

tornadoで立てたWebSocketで上記の設定を行った場合に403エラーが発生しました。

以下のコードを追加して、check originを行わないようにすると回避できた。これで良い訳じゃないんですがとりあえず

```diff
class WebSocketHandler(tornado.websocket.WebSocketHandler):
+    def check_origin(self, origin):  
+        return True  

    def open(self):
        if self not in cl:
            cl.append(self) 
```


# おわりに

Vue CLI3 の開発でWebSocket通信する場合の設定について書きました。
WebSocket通信する場合は参考にしてみてください。

設定の変更を繰り返していたら、パスに`/`を設定した場合も動いた時もあったので何かおかしいかもです。。。
知っている方いれば教えてください。