Twitterクローンを作ります #38 Socket.IOの確認

今回からチャット機能を作っていきます。

元の講座では Socket.io を利用していましたが、Cloud Run + Firebase Hosting で利用可能なのか一応確認しておきましょう。

下記のページを見る限り、Firebase Hosting の rewrites でリクエストを Cloud Run に流すことができれば実現はできそうです。

https://cloud.google.com/run/docs/triggering/websockets?hl=ja

https://cloud.google.com/run/docs/tutorials/websockets?hl=ja

# サーバ

flask-socketio というのがあるようなのでこちらを利用してみます。

https://github.com/miguelgrinberg/Flask-SocketIO

```server/requirements.txt
Flask
flask-socketio
pymongo[srv]
gunicorn
black
isort
```

通信ができるかどうかだけ確認できればいいので、適当に作ります。

```python:server/main.py
import logging
import os

from bson.objectid import ObjectId
from flask import Flask
from flask.json import JSONEncoder
from flask_socketio import SocketIO, emit

app = Flask(__name__)

...

socketio = SocketIO(app)


@socketio.on("connect")
def connect():
    app.logger.info("CONNECT")
    emit("connected", {"data": "connected!"})


@socketio.on("ev1")
def ev1():
    app.logger.info("EV1")
    emit("resp1", {"data": "resp1!"})


from apis import login, notifications, posts, users
```

# 画面

クライアント側は `socket.io-client` が必要になるのでインストールします。

```
$ npm install socket.io-client
```

こちらも適当に作ります。

```vue:client/src/views/HomeView.vue
<template>
  <div>
    <h3 class="uk-margin-top">Home</h3>
    <button @click="onClick">ev1</button>
    <post-edit></post-edit>
    <hr class="uk-margin-right" />
    <div>
      <post-panel
        v-for="post in posts"
        :key="post._id"
        :post="post"
      ></post-panel>
    </div>
  </div>
</template>

<script>
import { io } from "socket.io-client";
import { onMounted, computed } from "vue";
import { useStore } from "vuex";

import PostEdit from "@/components/PostEdit.vue";
import PostPanel from "@/components/PostPanel.vue";

export default {
  components: {
    PostEdit,
    PostPanel,
  },
  setup() {
    const store = useStore();

    onMounted(() => {
      store.dispatch("loadPosts");
    });

    const socket = io();
    const onClick = () => {
      socket.emit("ev1");
    };
    socket.on("resp1", (arg) => {
      console.log("resp1");
      console.log(arg);
    });
    socket.on("connected", (arg) => {
      console.log("connected");
      console.log(arg);
    });
    return {
      posts: computed(() => store.state.posts),
      onClick,
    };
  },
};
</script>
```

これで、ホーム画面が表示されたらSocket.IOの接続が確率されて、"connected"とログが出力され、"ev1"ボタンをクリックすると"resp1"のログが出力されるはずです。

ローカルで動くようにプロキシの設定をします。

```client/vue.config.js
const { defineConfig } = require("@vue/cli-service");
module.exports = defineConfig({
  transpileDependencies: true,
  devServer: {
    proxy: {
      "^/api/*": {
        target: "http://localhost:5000",
      },
      "^/socket.io/*": {
        target: "http://localhost:5000",
      },
    },
  },
});

```

ローカルでは問題なく動作しますね。

# デプロイ

`/socket.io/**` のリクエストを Cloud Run に流してあげれば動くのではないかなと思うのですが、うまくいくでしょうか？

firebase.json を下記のように修正します。

```client/firebase.json
{
  "hosting": {
    "public": "dist",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "/api/**",
        "run": {
          "serviceId": "toitta-api",
          "region": "asia-northeast1"
        }
      },
      {
        "source": "/socket.io/**",
        "run": {
          "serviceId": "toitta-api",
          "region": "asia-northeast1"
        }
      },
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}
```

この状態でコミットして GitHub Actions にデプロイしてもらいます。

![スクリーンショット 2022-03-30 18.49.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2536525/6e2e1bef-60cf-c0ce-66f9-ef8d222fff60.png)

通信自体はできているみたいですね!
400エラーになっている部分は "Invalid session" というエラーが帰ってきていました。

https://cloud.google.com/run/docs/triggering/websockets?hl=ja

にあるような状態の同期とかをまだしていないので別インスタンスにつながってエラーになったのかな、と思っていますがちゃんと調査できていないです。

通信自体は可能なようなので次回以降チャット機能を作っていきます。