既存のPHPサイトにNode.js+Socket.ioチャットボットを追加しセッションを共有する
既存のPHPサイトにチャットボットを追加しようという話があるのだが、そのサイトにはユーザ認証機能があり、チャットボットに関しても認証済みのユーザしかアクセスさせたくないという要件がある。
PHPサイトはCMSを利用しているため、こちらにはほとんど手を入れずに実装したいと考えているので、チャットボットとの会話インターフェースはPHPサイトにiframeを埋め込むこととし、iframeでロードするページはNode.jsから配信、Node.jsで配信されたページ内でSocket.ioを使ったチャットを行うという構成を考えている。
ここで問題になるのが、PHPとNode.js+Socket.io間でのセッションの共有をどうするかということ。それぞれ個別に認証機能を持つというのはあまりに利便性が悪いため、PHPで認証したユーザをNode.js側も認識できるようにしたい。
セッション共有の戦略
上記要件は一旦置いておいて、PHPとNode.jpでセッション共有する戦略を調べてみた。
セッションストアを共有する
参考: https://simplapi.wordpress.com/2012/04/11/php-nodejs-session-share-memcache/
PHPがセッションストア(ここではmemcache)に保存したセッション情報を、Node.jsが参照するという方法。PHPがユーザを認証してセッション情報をmemcacheに保存し、Node.jsはユーザから送信されたCookieからセッションIDを取得し、memcacheからセッション情報を取得するというもの。
一つ問題となるのは、PHPがセッション情報を独自の形式でシリアライズし、セッションストアに保存すること。例えば以下のような形。
user_id|s:1:"1";password|s:0:"";firstname|s:7:"Charles";
Node.jsでこれをそのまま扱うのは不便なので、上記サイトではPHPのセッションハンドラを改良し、セッションをJSON形式で保存することで、Node.jsから扱いやすいようにしている。
PHPにNode.jsからアクセスするためのAPIを用意する
参考: http://www.slideshare.net/takyam1213/php-meets-nodejs
セッションストアを共有するのではなく、PHPにAPIを用意して、Node.jsがAPIからセッション情報やユーザ情報を取得するという方法。Node.jsはセッションIDをWebsocketハンドシェイク時にCookieから取得し、そのCookieをHTTPヘッダにつけてAPIにアクセスすることで、PHPから見ると認証済みユーザからのアクセスとなる。
上記以外に、こちらの議論も参考になった。
https://groups.google.com/forum/#!topic/nodejs_jp/gU2347-33PQ
セッション共有を実装してみる
PHPサイトはCMSを利用しており、APIの追加開発等はしたくなかったので、セッションストアを共有する戦略でいこうと思う。
PHPの設定
セッションストアとしてRedisを使用するようにしたいので、例えばphp.ini
に以下の設定をする。
extension = redis.so
session.save_handler = redis
session.save_path = "tcp://127.0.0.1:6379"
上記参考記事で紹介したが、PHPはセッション情報を独自の形式でシリアライズするので、Node.jsからは扱いづらい。上記記事ではPHPのセッションハンドラを改良してJSON形式で保存するようにしていたが、もっと簡単な方法があった。
まず、PHPのシリアライズ形式を変更する。
;session.serialize_handler = php # コメントアウト
session.serialize_handler = php_serialize
php_serialize
ハンドラを使用すると、例えば以下のような形式にシリアライズされる。
$_SESSION["hoge"] = "foo";
$_SESSION["bar"] = "baz";
これがこうなる。
a:2:{s:4:"hoge";s:3:"foo";s:3:"bar";s:3:"baz";}
シリアライズされたセッション文字列をNode.jsで扱う
Node.jsのphp-serializeというモジュールを使用すると、上記形式のセッションをオブジェクトとして扱うことができるようになる。
例えばこんな感じ。
const phpSerializer = require('php-serialize');
const sessionStr = 'a:2:{s:4:"hoge";s:3:"foo";s:3:"bar";s:3:"baz";}';
const session = phpSerializer.unserialize(sessionStr);
console.log(`hoge: ${session.hoge}`); // hoge: foo
console.log(`bar: ${session.bar}`); // bar: baz
セッションミドルウェアを作成する
クライアントアクセス時にRedisからセッションを取得するミドルウェアを実装する。Node.jsのフレームワークはExpressを使っているとして、例えば以下のような感じ。モジュールのインポート文とかRedisクライアントの作成とか一部省略。
function session(req, res, next) {
if (!req.cookies) {
console.error('must use cookie-parser middleware');
return next();
}
const sessionId = req.cookies['PHPSESSID'];
if (!sessionId) {
return next();
}
const sessionKey = 'PHPREDIS_SESSION:' + sessionId;
redis.getAsync(sessionKey)
.then((sessionStr) => {
if (sessionStr) {
req.session = new Session(sessionKey, sessionStr);
}
return next();
})
.catch((err) => {
return next(new Error('Server error'));
});
};
}
function Session(sessionId, sessionStr) {
this.id = sessionId;
let data;
try {
data = phpSerializer.unserialize(sessionStr);
} catch(e) {
return;
}
for (const prop in data) {
if (!(prop in this)) {
this[prop] = data[prop];
}
}
}
req.cookies
にはクライアントが送信してきたCookieが入っていて、ここにPHPサイトで発行されたセッションIDが入っているのでそれを取得する。なお、req.cookies
が存在するためにはcookie-parserミドルウェアが前段に必要。
次に、Redisからセッション文字列を取得する。デフォルトだとPHPREDIS_SESSION:
というプレフィックスがついたキーで保存されている。例えばPHPREDIS_SESSION:30bn9bgoii4ndkii9grr128vf4
のような感じ。
セッション文字列が取得できたら、php-serializeでデシリアライズしてオブジェクトとしてreq.session
に格納する。
ログイン済みかどうか確認する
Socket.ioであれば例えば以下のようにミドルウェアを設定する。
const cookieParser = require('cookie-parser');
const io = require('socket.io')();
io.use((socket, next) => {
cookieParser()(socket.request, socket.request.res, next);
});
io.use((socket, next) => {
session(socket.request, socket.request.res, next);
});
io.use((socket, next) => {
if (socket.request.session.user_id) {
next();
} else {
next(new Error('authentication required');
}
});
io.on('connection', (socket) => {
...
});
ログイン済みかどうかを判定する部分はサイトによって異なるが、ここではセッションにユーザIDがあるかどうかを判定条件としている。ユーザIDが存在すればログイン済みとして先に進み、存在しなければWebsocketハンドシェイクに失敗するようにしている。
まとめ
PHPとNode.jsでセッションを共有したいという要件はまあまああるようで、php nodejs session
などでググるといくつか記事がヒットした。情報の多くは海外ブログやStackOverflowで、ガチャガチャとPHPをいじる感じの内容が多いが、PHPの設定を少し変えるのと、php-serializeという便利なモジュールのおかげで結構簡単に実装できた。
日本ではあまり事例がないのか、あるけどブログに上がっていないのかよくわからないが、同じようなことをしようとしてる人に少しでも参考になればと。
なお、今回はセッションを取得するだけだが、Node.js側で情報を追加して保存したいということもあるので、そのあたりをまた別の機会に。
Author And Source
この問題について(既存のPHPサイトにNode.js+Socket.ioチャットボットを追加しセッションを共有する), 我々は、より多くの情報をここで見つけました https://qiita.com/takehilo/items/bffb82b806c9170cf29b著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .