Quantcast
Channel: Node.jsタグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8691

Redisを使ってExpressとWebSocketのセッションを共有する

$
0
0

先日、WEBサイト制作者向けのウェブサービスをリリースしたのですが、その制作過程で得た知見をシリーズで発信していく記事の第3弾になります。
個人開発でウェブサービスにトライしてみたいと考えている方の参考になりましたら嬉しいです。

Node.jsサーバでWebSocketを使用する際、httpセッションを共有したいという場面が出てくると思います。
今回の記事では、httpセッションとWebSocketセッションを共有する方法について書いてみたいと思います。

Redisをセッションストアとして使う

実はRedisを使わなくても実現できるのですが、以下の理由によりRedisを使うこととします。

  • Nodoサーバで全部受け止めるのではなく負荷を分散させておきたい
  • 開発時にNodeサーバを起動し直してもセッションを維持できて便利
  • 複数のNodeサーバでセッションを共有できるので、機能によってサーバ(コンテナ)を分けられる

Redisをひとことで説明すると、ネットワーク接続されたインメモリデータストアで、主にキー・バリュー型のデータを扱うNoSQLデータベースのひとつというところでしょうか。

セッションデータをRedisに書き込むことにより、複数のプログラムから参照してセッションを共有しようという作戦です。

httpセッションをWebSocketで呼び出す

ちょっと長いですが、コードを提示します。

constexpress=require('express')constWebSocket=require('ws')constRedis=require('ioredis')constSession=require('express-session')constRedisStore=require('connect-redis')(Session)// ---------------------------------------------------// sessionデータを保管するstoreを用意する// ---------------------------------------------------// Redisのhostとportは環境変数から読み込むconst{REDIS_HOST,REDIS_PORT}=process.env// Redis用インスタンスを生成する// セッション&通知バッファリング用constredis=newRedis({host:REDIS_HOST||'redis',// ※Dockerで使用する前提でこんな風に設定してますport:REDIS_PORT||6379})// セッションストアを生成するconststore=newRedisStore({client:redis})// ---------------------------------------------------// ここからexpressの設定(httpセッション)// ---------------------------------------------------// expressを生成するconstapp=express()app.use(express.json())// Redisに紐づいたセッションプロバイダーを作るconstsession=Session({store,secret:'hogehoge',// お好きにsecretを指定してくださいresave:false,saveUninitialized:false,cookie:{secure:'auto'}})// expressにセッションプロバイダーを紐づけるapp.use(session)// port 3000 でlinten開始consthttpServer=app.listen(3000)// expressを初期化する段階で、Redisに紐づいたセッションプロバイダーと差し替えるだけなので、// すでに稼働しているプログラムの改変も最小限の作業で済む(かも)// あとは普通にexpressの処理を記述すればOK!.........// ログイン処理app.post('login',(req,res,next)=>{// ログインチェック......// ログイン完了時の処理// DBなどから取得したユーザ情報をセッションに書き込むイメージ user = ユーザ情報req.session.user=user// ログイン成功時にセッションID(req.sessionID)をクライアントに返す!  // これを後でWebSocketを通して返却してもらい紐づけに使用するべしres.json({login:true,sessId:req.sessionID})})// ---------------------------------------------------// ここからWebSocketの設定//// WebSocketサーバを生成するconstwsServer=newWebSocket.Server({server:httpServer.server})wsServer.on('connection',(ws)=>{ws.on('open',()=>{// ここでは、redisSessionId というパラメータにセッションIdを保管することにしますws.redisSessionId=''})ws.on('message',(message)=>{// データを復号するconstmsg=JSON.parse(message)// セッションidをwsに紐づける処理// この例では、クライアントから次のようなデータを受け取る想定// { cmd: 'connect_session', sessionID: 'xxxxxxxxxxxxx' }if(msg.cmd==='connect_session'){ws.redisSessionId=msg.sessionID// クライアントにセッションidを受け取ったことを報告しておくreturnws.send(JSON.stringify({result:'received_session_id'}))}......})......})// ---------------------------------------------------// 何らかのイベント処理(WebHookなど)//consthogeEvent=(params)=>{// wsServerには複数のsocketが接続されている// wsServer.clientsをループで処理して個々のWebSocketに対して処理を行うwsServer.clients.forEach(async(ws)=>{// Sessionを取得するconstsession=!ws.redisSessionId?awaitgetSession(ws.redisSessionId)// セッションデータ読込関数を呼ぶ:{}console.log(session.user)......})}// ---------------------------------------------------// セッションデータの読み込み関数//constgetSession=(sid)=>{returnnewPromise((resolve,reject)=>{// Redisに紐づいたセッションストアからセッションデータを取得するstore.get(sid,(err,session)=>{if(err){returnreject(err)}resolve(session?session:{})})})}

言葉で説明するとこんな感じです

  1. httpサーバ:セッションをRedisで管理するよう設定する
  2. httpサーバ:ログインが完了したらセッションIDをクライアントに返す
  3. クライアント:WebSocketでセッションIDを送信する
  4. WebSocketサーバ:受け取ったセッションIDをソケットに紐づけて記録しておく
  5. WebSocketサーバ:セッションIDを使ってRedisストアからセッションデータを読み出す

クライアント側のコーディングに関して注意が必要なのは、WebSocketのコネクションの状況(未接続・接続・切断)に応じて適切に処理を切り分けてやる点です。

このあたりは、要件に応じて正解が異なって来ますので、悩みどころではないかと思います。

宣伝です。。。

この記事の冒頭で触れたウェブサービスにも、この手法(異なる部分も多いですが)を利用しています。

WEBサイト制作者さん向けのサービスですので、お気軽にお試しいただけると幸いです。(無料で使えます)


Viewing all articles
Browse latest Browse all 8691

Trending Articles