📩

Docker × socket.io でroomsチャットを作成

2021/12/02に公開

はじめに

Docker × socket.ioの記事がない!!
roomsの作り方もわかんない!!
ということで一日クオリティで組んだコードを、コピペするだけで使えるようにかるーーーーく解説。
記事内の解説には誤りがある場合がございます。ご了承ください。
Dockerの仕組みや、socket.ioについてはもっと詳しい記事が多いため割愛。

また、roomsとは部屋割りのことです。

indexページで入力した名前とroomIdをchatページにURLパラメータとして渡すことで実装。
https://github.com/mitsuYashi/socket-chat

環境構築

node環境構築済みのかたは飛ばしていただいて構いません。
ディレクトリ構成

root
├ dockerfile
└ docker-compose.yml

とりあえずこれだけあれば大丈夫です。

dockerfile
FROM node:14.15.3
WORKDIR /usr/src/app
COPY . .
RUN yarn
docker-compose.yml
version: '3'
services:
  app:
    build: .
    volumes:
      - ./app:/usr/src/app
    ports:
      - 3000:3000
    command: sh -c 'node server.js'
    tty: true

以上をコピペしてもらえればOKです!
nodeのバージョンは安定板なら基本なんでも大丈夫なはずです。

terminal
docker-compose build
docker-compose run --rm app yarn add body-parser express socket.io

これで必要なライブラリのインストールは完了です。
yarnかnpmは個人の好みでお願いします。

コード

ディレクトリ構成

app
├ node_modules
├ public/
|  ├ js/
|  |  ├ room.js
|  |  └ socket.js
|  |
|  ├ chat.html
|  └ index.html
|
├ package.json
├ server.js
└ yarn.lock

server.jsとpublicフォルダを作成します。

server.jsはexpressによるルーティングと、ソケットのサーバーサイドを書きます。
public内は画面に表示するhtmlとフロントサイドのソケットです。

コード
server.js
const express = require("express");
const app = express();
const http = require("http").createServer(app);
const io = require("socket.io")(http);

let store = {};

app.use(express.static('public'));

app.get("/", (req, res) => {
  res.sendFile(__dirname + "/index.html");
});

app.get("/chat", (req, res) => {
  res.sendFile(__dirname + "/public/chat.html");
});

io.on("connection", (socket) => {
  socket.on("join", (msg) => {
    usrobj = {
      'room': msg.roomId,
      'name': msg.name
    }
    store = usrobj;
    socket.join(msg.roomId);
  })

  socket.on("post", (msg) => {
    io.to(store.room).emit("message", msg);
  });
});

http.listen(3000, () => {
    console.log("success listen 3000");
})
room.js
const socket = io();
const room = document.getElementById("room");
const user_name = document.getElementById("user_name");

document.getElementById("room-post").addEventListener("submit", (e) => {
  e.preventDefault();

  if (user_name.value === "") {
    user_name.value = "user1";
  }

  room.value === "" ? (room.value = 0000) : null;

  window.location.href =
    window.location +
    "chat?roomId=" +
    parseInt(room.value) +
    "&name=" +
    user_name.value;
});

socket.js
const socket = io();

const url = new URL(window.location.href);
const params = url.searchParams;

const urlRoomId = parseInt(params.get('roomId'));
const urlName = params.get('name');

socket.on("connect", () => {
  socket.emit("join", { roomId: urlRoomId, name: urlName });
})

document.getElementById("frm-post").addEventListener("submit", (e) => {
  e.preventDefault();

  const msg = document.getElementById("msg");
  if (msg.value === "") {
    return false;
  }
  
  socket.emit("post", { text: msg.value });

  msg.value = "";
});

socket.on("message", (msg) => {
  const list = document.getElementById("msglist");
  const li = document.createElement("li");
  li.innerHTML = `${msg.name}:${msg.text}`;
  list.insertBefore(li, list.firstChild);
});

window.addEventListener('load', () => {
  msg.focus();
})
index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h1>SocketIOチャット:RoomId</h1>
    <form id="room-post">
        <label>
            <h2>RoomId</h2>
            <input type="text" id="room" maxlength="4" placeholder="1234">
            <label>
                name
                <input type="text" id="user_name" name="name" value="user1">
            </label>
        </label>
        <input type="submit" value="入室">
    </form>

    <script src="socket.io/socket.io.min.js"></script>
    <script src="./js/room.js"></script>
</body>
</html>
chat.html
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>SocketIOチャット</title>
</head>

<body>

    <h1>SocketIOチャット</h1>

    <form id="frm-post">
        <label>
            comment
            <input type="text" id="msg" autocomplete="off">
        </label>
        <button>送信</button>
    </form>

    <ul id="msglist">
    </ul>

    <script src="socket.io/socket.io.min.js"></script>
    <script src="./js/socket.js"></script>
</body>

</html>

これをコピペするだけで完成するので、あとは

terminal
docker-compose up

で起動します。
https://localhost:3000
ここに接続するだけでおわり。

完成イメージ

解説

htmlはformを置くだけなので割愛。

server.js
const express = require("express");
const app = express();
const http = require("http").createServer(app);
const io = require("socket.io")(http);

必要なライブラリを呼び出しています。

let store = {};

空の配列を作成。後に使います。

app.use(express.static('public'));

app.get("/", (req, res) => {
  res.sendFile(__dirname + "/index.html");
});

app.get("/chat", (req, res) => {
  res.sendFile(__dirname + "/public/chat.html");
});

expressのルーティングです。app.useでルートフォルダをpublicに指定しています。
これがないとhtmlからjsファイルやcssファイルを読み込めません。
app.getでそのURLにアクセスがあったときに表示するファイルを選択しています。

io.on("connection", (socket) => {
  
});

connectionイベントでソケット通信を開始時の処理をします。

socket.on("join", (msg) => {
    usrobj = {
      'room': msg.roomId,
      'name': msg.name
    }
    store = usrobj;
    socket.join(msg.roomId);
  })

  socket.on("post", (msg) => {
    io.to(store.room).emit("message", msg);
  });

.onは受信。.emitは送信の処理をしています。
joinイベントを受け取ることでroomに入る処理を行います。
socket.joinの引数で共通のroomに入室できます。
postイベントでメッセージの送信を行います。
io.toに指定した引数の部屋に引数msgを送信しています。

http.listen(3000, () => {
    console.log("success listen 3000");
})

localhost:3000でサーバーをたて、成功した場合console.logで出力しています。

room.js

ここからはsocket部分に絞って解説

window.location.href =
    window.location +
    "chat?roomId=" +
    parseInt(room.value) +
    "&name=" +
    user_name.value;

入力されたroomIdとnameをURLパラメータとして生成し、遷移。
indexはこれだけ。

chat.js
const socket = io();

const url = new URL(window.location.href);
const params = url.searchParams;
const urlRoomId = parseInt(params.get('roomId'));
const urlName = params.get('name');

1行目でsocket.ioを呼び出しているのではなく、ソケット通信が開通されるらしいです。
初見じゃわからない。
下4行はURLパラメータの取得です。

socket.on("connect", () => {
  socket.emit("join", { roomId: urlRoomId, name: urlName });
})

connectイベントでソケットに接続します。
joinイベントの引数にroomIdとnameを指定します。投稿はこの時にサーバに送信された名前で行われます。また、このroomIdが同じ人とチャットが可能です。

e.preventDefault();

jsの関数で、formの画面移動のかかる挙動を無視します。

const msg = document.getElementById("msg");
  if (msg.value === "") {
    return false;
}

入力欄が空の場合メッセージが送信されないようにしています。

socket.emit("post", { text: msg.value });

postイベントでメッセージを送信します。

socket.on("message", (msg) => {
  const list = document.getElementById("msglist");
  const li = document.createElement("li");
  li.innerHTML = `${msg.name}:${msg.text}`;
  list.insertBefore(li, list.firstChild);
});

メッセージを受け取りリストタグで表示させています。
ここのmessageイベント名はバック側と同じになるように決めてください。

最後に

コードをアコーディオンにしているので、見逃している方は気を付けてください。

TECHCAFE×TAIRのアドベントカレンダー用に作成しました記事です。専門がWEBアプリのため、TAIRとは関わりがないです。お許しください。

Discussion