今回は画像ファイルをExpress
を使わずSocket.io
だけでクライアントに送信して表示する。
やりたいことは
「Node
サーバーから画像切り替えのメッセージ → Vue
クライアントの画像を切り替え」
実はこれは難しいことを考えずにExpress
を導入してhttp
サーバー越しに画像のURL
だけSocket.io
で送信すれば、画像をメッセージで切り替えることができるのだが
少し高度なテクニックとして今回はこれをSocket.io
だけで再現する。
Express
を使った時のデメリット一応Express
+Socket.io
で動的に画像を切り替えるシステムのデメリットを挙げてみた。
HTTP
サーバを立てるためSocket.io
とは別のポートが解放される
送信する画像ファイルはURL
(ファイル名)さえわかれば誰でもアクセスできてしまう
Express
のインストール+HTTP
サーバー用コードを追加する必要がある
ファイルをフォルダではなく個別に管理する場合ルーティングをする必要がある。
(逆を言えばこの手間からstatic
でフォルダごと公開することになる可能性大)
私自身Express
にそれほど詳しくなくもし間違っていたら申し訳ないが、多かれ少なかれデメリットはある。
以上のデメリットが致命的になることはさほどないが、そもそもExpress
を必要としないシステムなのであればわざわざExpress
をインストールして画像を送信するhttp
サーバを追加する必要はない。
※Socket.io
の基本的なコーディングに関しては他のサイト参照のこと。
今回は「画像」と書いたがSocket.io
ではこの方法でバイナリファイルでもなんでも送信できる。
こんな感じで動作する。
画面は一切遷移していない。
Vue
側
※いつも通りVuetify
を使っているがv-btn
はbutton
で、v-img
はimg
タグでも置き換え可能。Vuetify
を使うとcss
による整形が不要になるのでオススメ。
<template>
<v-container>
<v-btn @click="getImg()">get img</v-btn>
<v-img :src="imgDataBase64" v-if="imgDataBase64" />
</v-container>
</template>
<script>
import io from "socket.io-client";
export default {
name: "HelloWorld",
data: () => ({
socket: io("localhost:7000"),
imgDataBase64:null,
}),
mounted() {
this.socket.on("connect", () => {
console.log("connected");
this.socket.on("img_data",imgDataBase64=>{
this.imgDataBase64 = imgDataBase64
})
});
},
methods: {
getImg() {
this.socket.emit("get_img");
}
}
};
</script>
Node.js側
import io from 'socket.io'
import fs from 'fs'
const socket = io(7000)
socket.on('connection', function (socket) {
console.log(`a user connected[id:${socket.id}]`)
socket.on("get_img", () => {
const filePath = "./images/test_img.jpg"
const base64JpgHeader = "data:image/jpeg;base64,"
const img = fs.readFileSync(filePath,'base64')
const img_base64 = base64JpgHeader + img
socket.emit("img_data",(img_base64))
console.log("a image was sent")
})
})
画像はfs
でbase64
でエンコードされた形式で読み込む。これによりバイナリファイルも読み込みが可能。
このbase64
形式はバイナリファイルをテキストファイルに変換するので少々長いデータにはなるがバイナリが扱えない通信システムに流すことができる。
vue
側のv-img
に入っているsrc
を見てみると画像URLではなくこのbase64
が入ったデータを直接引き渡している。
Vue
のimg
タグはこのbase64
データをそのまま「画像」として表示することができるのでデコードが必要ない。これが便利。
<v-img :src="imgDataBase64" v-if="imgDataBase64" />
ただし条件があるので次に書く
Node.js
のエンコードしている部分で文字列によるヘッダを付与している。
const base64JpgHeader = "data:image/jpeg;base64,"
const img = fs.readFileSync(filePath,'base64')
const img_base64 = base64JpgHeader + img
ブラウザでデコードなしでダイレクトにbase64
画像を表示する場合、このヘッダがないとエラーとなり表示されない。
Socket.io
だけで配信する場合のデメリットももちろんある。
Express
はディレクトリごと公開となるが、こちらはファイル単体ごとで制御が可能。
接続してきているユーザーごとに表示される画像を切り替えられ許可のない画像は配信されない。
ファイルとして出力しないので、ディスクのIOが減るなどのメリットもある。
ディスクIOを消費する可能性がある動的なサムネイル生成なんかはメモリだけで完結できるので得意そうだ。
Express
であればファイルが生成される前にURL
を配信してしまいブラウザ側でエラーとなる可能性もある。
Express
のようにディレクトリまるごと送信(たくさんの画像を一度に送信)しようとした場合、複数のファイルを明示的に読み込んで処理し一括で送信するコードを実装する必要があるのでコード量で言えば手間。
また画像の種類によって付与するヘッダを分ける必要があるかもしれない
※十分にテストしていないがpng
などもimage/jpeg
で表示されるのは確認
base64
変換する必要ある?そもそも古い習慣でテキスト(base64
)にエンコードしているが、わざわざ変換しなくてもバイナリで送ったほうが良いのではと記事を執筆していて思ってしまった。
socket.io
でバイナリが送れるはずなのでbase64
にエンコードすることは不要だ(通信量が30%削減できるらしい)
次回はこの手法に対応した記事を執筆予定。
2020/06/21:続きを執筆した
Socket.ioでバイナリ画像を受信してVue.jsで表示
Socket.io
+Vue.js
を使った可能性は無限だ。
ユーザーの操作に関係なくサーバー側からユーザー側に表示される画像をメッセージでコントロールできるので簡単なゲーム等も作れそうだ。
とはいえ今回の記事のようにSocket.io
だけで画像送信が出来たらなにか得か?といわれたらそれほどメリットがあるわけではない。
よほど画像の送信先を厳密に管理したい時だけだろう。
ニッチな需要ではあるが機会があれば使ってみてほしい。