Node.jsのsokect.ioを使ったPUSH通知の仕組みを作ってみた
はじめに
こんにちは、わたる です。
50 の手習いで Web アプリを作ってみよう、と色々と調べておりまして、自分の備忘録もかねて作ってみた実装をこちらに投稿しようと思います。
今回のプログラム
任意のタイミングでサーバーからクライアントへ PUSH する仕組みを持つ環境を Node.js を使って構築してみました。
今回は PUSH の仕組みは socket.io を使い、この仕組みの実装につき、Node.js の I/F は http の平文にしていますこと、ご了承ください。
処理概要
以下のようなイメージで構築しようと考えております。
1.クライアントから接続要求
2.サーバーにてクライアント情報を追加
3.クライアントから受付要求
4.サーバーにて登録処理して応答
5.サーバーからは PUSH 用の RestAPI を用意して任意のタイミングでクライアントへ PUSH 通知
処理イメージ
ポンチ画レベルのシーケンスで恐縮ですが 1.~5. の処理イメージを描いてみました。
※フォントは「Myrica P」を使ってエクセルで編集し、その図をスクリーンショットとして画像に落として表示しています。
ソース
ファイル構成
ファイル構成は下記になります。
napi_sample
┣━ client.js
┣━ players.json
┣━ interface.js
┗━ push.js
サーバー側
サーバー側のソースです。
いずれも 55555 ポートにて接続できるようにしました。
※処理イメージには記載しませんでしたが、一覧メンバーを取得する「get_member」というAPIも実装しています。
const express = require('express') ;
const { createServer } = require('http') ;
const { Server } = require('socket.io') ;
const app = express() ;
const httpServer = createServer(app) ;
const io = new Server(httpServer) ;
var count = 0 ;
// JSON を受け取れる仕掛け構築
app.use(express.urlencoded({extended: true})) ;
app.use(express.json()) ;
const MEMBER = {} ;
// 以下を想定
// {
// "socket.id" : {name:"user1", count:"1"},
// "socket.id" : {name:"user2", count:"2"}
// }
// do_push(Internal RestAPI)
app.post('/do_push', (req, res) => {
if (Number(req.body.count) === 0) {
// 全メンバーへPUSH
io.emit('push', req.body.message) ;
} else {
socketid = getIdFromCount(req.body.count) ;
if (socketid === null) {
console.log('not found : ' + req.body.count) ;
console.log(MEMBER) ;
} else {
// 見つけたメンバーへPUSH通知
io.to(socketid).emit('push', req.body.message) ;
}
}
res.sendStatus(200) ;
}) ;
// メンバー一覧取得
app.get('/get_member', (req, res) => {
// MEMBERをJSONで応答
res.status(200).json(MEMBER) ;
}) ;
// socket.io 接続イベント
io.on('connection', (socket) => {
// socket.id でメンバー登録
MEMBER[socket.id] = {
name:null, count:0
} ;
// クライアントからの切断イベント
socket.on('disconnect', (reason) => {
// 当該メンバーの削除
delete MEMBER[socket.id] ;
}) ;
// クライアントからの受付イベント
socket.on('reception', (data) => {
// TODO: 既に接続済みであれば切断する
// TODO: 同時アクセス時の count の排他制御の検討が必要
// 登録名を更新
count = count + 1 ;
MEMBER[socket.id].name = data.name ;
MEMBER[socket.id].count = count ;
// 受付結果をPUSH通知
io.to(socket.id).emit('recept_result', { result : true }) ;
}) ;
}) ;
function getIdFromCount(count) {
for( let key in MEMBER ) {
if (Number(MEMBER[key].count) === Number(count)) {
return key ;
}
}
return null ;
}
// listen
httpServer.listen(55555, () => {
console.log('Start. port on 55555.') ;
}) ;
管理するユーザ一覧
interface.js からロードするユーザ一覧は以下の json で定義しました。
{
"0001" : { "name" : "user1" },
"0002" : { "name" : "user2" },
"0003" : { "name" : "user3" },
"0004" : { "name" : "user4" }
}
クライアント側ソース
クライアント側のソースになります。
var client = require('socket.io-client') ;
const URL = 'http://サーバーIPアドレス:55555' ;
const username = process.argv[2] ;
console.log('try to connect as ' + username + '.') ;
// 接続
var socket = client.connect(URL) ;
socket.on('connect', () => {
// 接続時に受付へ
socket.emit('reception', {name : username}) ;
}) ;
// 受付結果受信イベント
socket.on('recept_result', (data) => {
console.log('Reception result : ' + data.result) ;
}) ;
// PUSH通知
socket.on('push', (msg) => {
console.log('Receive : ' + msg) ;
}) ;
// サーバーから切断で終了
socket.on('disconnect', (reason) => {
process.exit(0) ;
}) ;
PUSH通知サンプル
do_push を叩くサンプルの JavaScript は下記です。
var request = require('request') ;
var options = {
uri: "http://サーバーIPアドレス:55555/do_push",
headers: {
"Content-type": "application/json"
},
json:{
"count": null,
"message": null
}
} ;
options.json.count = process.argv[2] ;
options.json.message = process.argv[3] ;
request.post(options, (error, res, body) => {
}) ;
動作確認
サーバーは下記のようにして起動するとメッセージが出ます。
$ node interface.js
Start. port on 55555.
その後、クライアント接続を下記のようにして起動するとメッセージが出て接続に行きます。
$ node client.js user1
try to connect as user1
いくつかクライアントを接続しておいた状態で、do_push を行いますが、サンプルは
$ node push.js 1 'Test.'
とすると、最初に接続したクライアントに
Receive : Test.
と出るはずです。
すべてのクライアントへの通知を行いたい場合は
$ node push.js 0 'Message to ALL.'
とやるとすべてのクライアントにて、
Receive : Message to ALL.
と出るはずです。
終了
終了はサーバーにて Ctrl+C するとすべての接続しているクライアントも終了します。
クライアント側の単独の終了はクライアント側にで Ctrl+C すると終了します。
その際、サーバー側は MEMBER 配列から当該データを delete するようにしております。
おわりに
排他制御策等の TODO がありますが、基本的な動きは構築出来ました。
一応 github にも格納しております。[1]
また TIPS 的なものが出来次第、投稿しようと思います。
参考文献
socket.io Documentation
はじめてのSocket.io #3 チャット編「ユーザー間でのなりすましを防ぐ」
-
client.js、push.jsの接続先は「localhost」にしていますが、サーバのIPを変えて接続することも出来ますので適宜編集してください。 ↩︎
Discussion