node.js - socket.io - rails - sessionを共有する方法

railsで他言語とsessionを共有する際問題となるのが、
内部的にmarshalという形式でデータを保持しているため、
ruby以外の言語でsessionを取得しようとすると、marshalのdeserializeが出来ないとセッションデータを読み込むことが出来ません。

そのため、セッションを共有するために

・セッションデータの保持形式を marshal -> yaml or json or messagepack ....に変更

・ついでにセッションデータをクッキーではなくdb保存に変更

の2つを行います。

※今回はデータ保持にjson , セッションdbに mongodbを使います。

まずrails、node.js両者でmongoとsessionライブラリを用意。

rails

type - orm
library - mongo_mapper
git - http://mongomapper.com/

type - session store
library - mongo_session_store
git - https://github.com/brianhempel/mongo_session_store

# /path/to/app/Gemfile
gem 'mongo_mapper'
gem 'bson_ext'
gem 'mongo_ext'
gem "mongo_session_store-rails3"

セットアップはgit参照

node.js
library - mongoose
git - https://github.com/LearnBoost/mongoose

npm install mongoose

セットアップはgit参照


次にrailsでデータ保持形式を marshal -> json に変更するためモンキーパッチを充てます。


# /path/to/app/config/initializers/session_store.rb

YourApp::Application.config.session_store :mongo_mapper_store

module ActionDispatch
  module Session
    class MongoMapperStore < MongoStoreBase
      class Session
        include MongoMapper::Document
        set_collection_name MongoSessionStore.collection_name
        key :_id,  String                    
        key :data, String, :default => {}.to_json()
        timestamps!
      end
    end

    class MongoStore < MongoStoreBase
      class Session
        def initialize(options = { })
          @_id        = options[:_id]                    
          @data       = options[:data] || {}.to_json
          @created_at = options[:created_at]
          @updated_at = options[:updated_at]
	end
      end
    end

    class MongoStoreBase < AbstractStore
      private
      def pack(data)
	data.to_json                    
      end

      def unpack(packed)
	return nil unless packed
	data = JSON.parse(packed)
	if data.has_key?('flash')
          data['flash'] = ActionDispatch::Flash::FlashHash.new.update(Hash[*data['flash']])
        end
        data
      end
    end
  end
end

変更点は

・ソースのMarshalを使っていた部分をjsonに直しただけ

・なんかエラーが出たので微修正。参考は以下

http://memo.yomukaku.net/entries/314

railsについてはjson形式でデータが保持されるはずです。

あとはmongooseでsessiondb読むだけです。

ついでにsocket.ioのhandshake時にsessionを書き込みます。

大体こんな感じになると思います。

※以下はcoffeescriptです

io = require('socket.io').listen(3002)
connect = require('connect')
sys = require('sys')
util = require('util')
events = require("events")
mongo = require('mongoose');

mongo.connect('mongodb://localhost/your_session_db')
Schema = mongo.Schema
SessionSchema = new Schema
  _id: { type: String, required: true, unique: true }
  data: { type: String, default: '{}' }
  expires: { type: Date, index: true }

Session = mongo.model('Session', SessionSchema)

# config                                                                                                                                                                                         
io.configure ->
  io.set 'authorization', (handshake,callback) ->
    cookie = handshake.headers.cookie
    sessid = parseCookie(cookie)['_yoursession_id']
    Session.findOne { _id : sessid},(err,session) ->
      if err
        return callback(err,false)
      else
        handshake.session = JSON.parse(session.data)
        return callback(err,true)

io.of('/channels').on 'connection', (sock) ->
  console.log sock.handshake.session


なんか不具合出るんじゃないかと不安なので、不具合出た方連絡下さい。