今回は画像ファイルを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だけで画像送信が出来たらなにか得か?といわれたらそれほどメリットがあるわけではない。
よほど画像の送信先を厳密に管理したい時だけだろう。
ニッチな需要ではあるが機会があれば使ってみてほしい。
