SocketStream - 外部ライブラリ使用方法

Socketstreamはじめました。

今回は表題について説明します。


・config/app.coffee

・enviroments/.coffee



が設定ファイルです。
部はdevelopment,staging,productionです。


$ socketstream console

SocketStream > SS.config

で設定値が見れます。

configのpack_assetsがtrueだとsocketstream起動時に自動で読み込んでくれます。

というわけで

#/path/to/config/app.coffee

exports.config =
  pack_assets:    true
  ...

を追記。


次に外部ライブラリの配置場所は

lib/clients|server

下です。

説明が見つかりませんでしたが、多分1..js,2..js
などファイル名の数字prefixで読み込む順番を指定します。


試しにBackbone.jsをlibに置いてみる。


$ ls client/
1.jquery.min.js  2.jquery.tmpl.min.js  3.helpers.js  4.underscore-min.js  5.backbone-min.js


って感じで


$ socketstream s

16 Dec 05:44:53 - Pre-packing all client assets...
16 Dec 05:44:53 -   Concatenating file 1.jquery.min.js
16 Dec 05:44:53 -   Concatenating file 2.jquery.tmpl.min.js
16 Dec 05:44:53 -   Concatenating file 3.helpers.js
16 Dec 05:44:53 -   Minified 3.helpers.js from 4.439 KB to 2.1 KB
16 Dec 05:44:53 -   Concatenating file 4.underscore-min.js
16 Dec 05:44:56 -   Minified 4.underscore-min.js from 11.608 KB to 11.057 KB
16 Dec 05:44:56 -   Concatenating file 5.backbone-min.js
16 Dec 05:44:56 -   Minified 5.backbone-min.js from 13.804 KB to 13.473 KB
16 Dec 05:44:56 -   Appending SocketStream client files...
16 Dec 05:44:56 -   Concatenating file reset.css
16 Dec 05:44:56 - CSS libs concatenated
16 Dec 05:44:56 -   Compiling and adding app/client/app.coffee
16 Dec 05:44:56 -   Compiling and adding app/client/demo.coffee
16 Dec 05:44:56 -   Minified application code from 1.221 KB to 0.861 KB
16 Dec 05:44:56 - Stylus files compiled into CSS
16 Dec 05:44:56 - Compiled app.jade to index.html
   info  - socket.io started

------------------------------ SocketStream ------------------------------
  Version 0.2.7 running in development on PID 5103
  Primary web server listening on http://0.0.0.0:3000
  Spawned 1 back end worker process (PID 5107)
--------------------------------------------------------------------------
...


おkkk。

コンパイルされてコンプレスされて自動読み込みされる/assets/lib_[\d+].jsファイルが出来上がります。

でも連番とか気持ち悪いからrailsのassets pipelineみたいな感じで読み込めないかなー

Socket.ioでクライアントオブジェクトにメソッドを追加する方法 socket.io - node.js

以下coffeescriptになります。
ちなみにsocket.ioのバージョンは0.8.7になります。

socket.io@0.8.7 ./node_modules/socket.io
├── policyfile@0.0.4
└── socket.io-client@0.8.7



channel = io.of('/channels').on 'connection', (client) ->
  client.hoge() # => "hoge!"

Socket = require('socket.io/lib/socket.js')
Socket.prototype.hoge = -> "hoge!"


以上になります。

EventMachine,Sinatraのインテグレーション - eventmachine - sinatra - routing

eventmachineでルーティングが必要になりライブラリを探していたのですが、
sinatraと組み合わせるのが一番簡単そうです。

# /path/to/emapp.rb

%w(

rubygems eventmachine shout sinatra/base

).each { |lib| require lib}

EventMachine::run do
  class App < Sinatra::Base
    get '/' do
    end
  end
  App.run!(:port => 3000)
end

でおkです。
時間があったらwebsocket + EventMachineのインテグレーションも紹介したいと思います。


参考

Any success with Sinatra working together with EventMachine WebSockets? - stack overflow

rails + Backbone.jsでhaml-jsをテンプレートとして使う方法- Backbone.js - rails- jammit

まずさらっと紹介。


backbone.js
…クライアントサイドjsにMVCを提供してくれるすんげーライブラリ。railsと相性抜群(っぽい。まだサラっとしか見てません)

railsと連携してルーティングとか使ってみたいので、knockoutjs使ってましたがこっちに切り替えようかなと思ってます。


jammit
…jsのテンプレートとかcssとかをパッケージングしてくれるライブラリ。backbone.jsのviewテンプレートについて調べてたら出てきました。


haml
…htmlの省略記法を提供するマークアップ

Backbone is agnostic with respect to your preferred method of HTML templating. Your render function could even munge together an HTML string, or use document.createElement to generate a DOM tree. However, we suggest choosing a nice JavaScript templating library. Mustache.js, Haml-js, and Eco are all fine alternatives. Because Underscore.js is already on the page, _.template is available, and is an excellent choice if you've already XSS-sanitized your interpolated data.

Backboneのドキュメントより

ってことで、慣れてることもありテンプレートはHaml-jsを使いたいと思います。






まずjammitインストール

# /yourapp/Gemfile
gem "jammit"

でbundle

次にjammitが使うテンプレートフォルダと拡張子、javascriptをincludeする際のkeyを指定

# /yourapp/config/assets.yml
embed_assets: on

#拡張子の追加
template_extension: jst.haml
template_function: Haml

javascripts:
 #includeする際のkey
  workspace:
   #テンプレートフォルダとファイルマッチ
    - app/views/**/*.jst.haml

って感じになる思います。

あとはhttps://github.com/visionmedia/haml.js/
をダウンロードしてpublic or vendor下に配置

最後に、ヘッダで先ほど設定したjammitのキーでjsを読み込みます。

- #/yourapp/app/views/layouts/application.html.haml.or.something
!!!
%html
  %head
    = javascript_include_tag "/haml-js/lib/haml.js"
    = include_javascripts :workspace
    ...

以上です。

# /yourapp/app/views/some/thing.jst.haml
%h1 hello haml js world

みたいなの書くとjammit君がコンパイルしてくれて

/* /assets/workspace.jst.haml */
(function(){
window.JST = window.JST || {};
window.JST['some/thing'] = Haml('%h1 hello haml js world\n\n');
})();

こんなん生成してくれます。


参考 -

haml-js,jammit設定方法について
backboneでのベストなhamltemplateの使い方について - stack overflow
knockoutjsとbackbone.jsどっちがいいかについて - stack overflow

Backbone seeks to be more a full MVC platform, whereas Knockout.js gives you a foundation to apply MVVM/MVP and go from there.

knockoutjsとbackbone.jsどっちがいいかについて より

辺りが議論呼んでそうですね。

knockoutjs1〜2週間くらい触りましたが*observable*とデータバインドの威力は絶大でした。

データを更新するとUIが自動的に書き換わるのは書いててすげー衝撃的な楽さでした。

ただMVVMに慣れていないせいかデータ定義とメソッドがごっちゃになってしまって、コード量が増えてくるとリファクタに手こずった、というのが第一の感想です。

まだMVVMのアーキテクチャへの理解が深まる前ですが、Backbone.jsも試してみたいと思います。

rails - redis - zrange with scores - ubuntu - インストール + zrange でWITHSCORESをつけるオプション

ライブラリは以下を使用

type : redis client
library : redis-rb
git : https://github.com/ezmobius/redis-rb

# /path/to/app/Gemfile

gem 'redis'

でbundle install


あまり乗り気じゃないけどグローバル変数インスタンス突っ込む。

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

$redis = Redis.new(:host => 'localhost', :port => 6379)

$redis.zrange("yourzset", 0, 10,'WITHSCORES')

とかやってみたけどscoreがとれなかった。
gitのソースを見てみると

 def zrange(key, start, stop, options = {})
    command = CommandOptions.new(options) do |c|
      c.bool :withscores
      c.bool :with_scores
    end

    synchronize do
      @client.call [:zrange, key, start, stop, *command.to_a]
    end
  end


となっていたので

$redis.zrange("yourzset", 0, 10,:withscores => true)

でとれた。

node.js - socket.io - Chat Room - NameSpace - 名前空間 | emitの範囲 を分ける方法まとめ

必要なメソッド : to , join , leave, of , clients

tips1 : namespaceについてはsocket.ioのHow To Useページを参照
tips2 : toを使うとidにjoinしたメンバーだけに送られる
tips3 : clientsメソッドでroomidにjoinしているメンバーのsocketが得られる
tips4 : leaveでroomから出る

tips3については先日のnode.js - socket.io - rails - sessionを共有する方法で紹介したsessionの保存と合わせて
roomにjoinしているメンバーのhandshakeデータからセッションデータを引っ張ってこれます。


下記はcoffeescriptのソースになります。

Client

#util
p = (data)-> console.log data

#channel
class Channel
  constructor : (@roomid)->
    #tips1
    @sock = io.connect(youriohost + '/channels')
    @sock.emit('join',@roomid)
    #tips2
    @sock.on 'room message',(msg) ->
      p msg
    #*tips3
   @sock.on 'members in room',(members) ->
      p members

  sendMessage : (message) ->
    @sock.emit('sendMessage',@roomid,message)

channel = new Channel(roomid)
channel.sendMessage("only roomid's member can listen")

Server

io = require('socket.io').listen(3002)
connect = require('connect')
util = require('util')

#tips1
channel = io.of('/channels').on 'connection', (sock) ->
  sock.on 'join',(roomid) ->
    sock.join(roomid)
    sock.set('roomid',roomid)
    #*tips3
   sock.emit('members in room',channel.findRoomClients(roomid))

  sock.on 'sendMessage',(id,message) ->
    #tips2
    channel.to(id).emit('room message',message)

  sock.on 'disconnect' , () ->
    sock.get 'roomid',(err,roomid) ->
      #tips4
      sock.leave(roomid)

#*tips3
channel.findRoomClients = (roomid)->
  this.clients(roomid).map (client) ->
    client.what_you_want
    client.handshake.session.id
    client.handshake.session.name

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


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