0

node-methodmissingを高速化した

methodmissingというnpmがあって、rubyのmethodmissingと同じく存在しなメソッドを呼び出した時にno method errorにするのではなく、メソッド名と引数を横取りできる。


使用例

mm = require 'methodmissing'

class Foo
baz: "bazbaz"

foo = new Foo()

## methodmissingを適用
foo = mm foo, (func_name, args) ->
console.log "missing-method '#{func_name}' called"
console.log args


## 普通のメソッド呼び出し
console.log foo.baz

## 存在しないメソッドを呼び出す
foo.kazusuke("niku", "beer", "rice")

実行結果
bazbaz
missing-method 'kazusuke' called
{ '0': 'niku', '1': 'beer', '2': 'rice' }


存在しないメソッドが呼び出された時に、動的にメソッドが存在するフリをできるのはWebAPIのラッパーを作る時などに便利。


高速化


ベンチマークを取ってみたら、
存在しないメソッド呼び出しをキャプチャーするのは6〜7倍ぐらい速くなった。普通のメソッド呼び出しと比べて10倍遅い程度。
また、methodmissing適用した後のオブジェクトは通常のメソッド呼び出しが100倍ぐらい遅くなっていたのを、9倍遅い程度に抑えられた。

(methodmissingでメソッドを呼び出すコストが通常のメソッド呼び出しに比べて10倍ぐらい遅くなるだけで、呼び出されたメソッド全体の速度が遅くなるわけではないので、ループで何万回も呼び出す等でない限りこのコストは無視していいと思う)


といっても修正したの1行だけなんだけど、けっこうこういうので変わるものだな

存在しないプロパティを呼び出した時の動作が遅いのがまだ気になる。

0

socket.ioのroom機能を使ってchatを作った

rubyではなくNodeのSocket.io上にlindaを実装しようと思っていて、そのために

  • socketio clientをグループに分けるroom機能について知りたい
  • socketioを拡張するnpmをどうやって実装するのがいいのか調べる
必要があったので、chatを作った。


このようにshebangごとに部屋が別れる。

最近websocketがherokuで使えるようになったのでherokuに置いた。

ソースコード https://github.com/shokai/socketio-chat-study


実装

サーバーもクライアントも40行ぐらいでできた。express等WAFは使っていなくて、HTMLやJSをサーブする部分はhttpモジュールで書いた。
httpのインスタンス作る時にsocketioのインスタンスを食わせると、socketio.jsなどが生えるという仕様は面白い。nodeにはWSGI/Rack/PSGIのような物が必要ないように見える。


roomにクライアントを追加するには、サーバー側でsocket.join(‘部屋名’)で追加する。
クライアントが切断されてもroomから自動的に抜けてくれるわけではなく、その部屋に新しいクライアントをjoinした時にようやく切断済みクライアントがリストから取り除かれる。
なのでクライアントのdisconnectイベントでleaveさせた。

client.coffee
接続できたら、shebangから部屋名を決めてサーバーに”join_to_room”イベントを送る
socket.on 'connect', ->
room =
if (shebang = location.href.match(/#([^#]+)$/))
shebang[1]
else
'room1'
socket.emit 'join_to_room', room ## 部屋に入れてもらう

server.coffee
新規クライアントが接続してきて、”join_to_room”が来たら部屋に入れる。onceなので複数の部屋に1つのクライアントが入ることはない。
io.sockets.on 'connection', (socket) ->
socket.emit 'chat', {msg: 'hello new client!'}

## クライアントを部屋に追加する
socket.once 'join_to_room', (room) ->
console.log "<#{socket.id}> join_room to \"#{room}\""
socket.join room

## echo to room
socket.on 'chat', (data) ->
console.log "<#{socket.id}> #{data.msg}"
io.sockets.to(room).emit 'chat', data ## 部屋の全員にecho

socket.once 'disconnect', ->
socket.leave room ## 切断したら部屋から退去
notify_rooms()

notify_rooms()

## 現在存在する部屋名と人数を全クライアントに通知
notify_rooms = ->
rooms = {}
for name, ids of io.sockets.manager.rooms
## 全てのクライアントはデフォルトで "" という部屋に入っている
## "/" から始まる部屋名はshebangで指定された部屋
if name.match /^\/.+$/
name = name.replace /^\//, ''
rooms[name] = ids.length
io.sockets.emit 'rooms', rooms


Herokuでwebsocket

Using WebSockets on Heroku with Node.js | Heroku Dev Center

コマンド1発でherokuでwebsocketが使えるようになる。
heroku labs:enable websockets


Herokuへのデプロイは簡単で、

  • websocket proxyが通るように、httpとwebsocketは同じportにする
  • 環境変数PORTを読んでwebサーバーを起動する
  • 起動コマンドをProcfileに書く
  • npmの依存パッケージはpackage.json
さえ守っていればすぐHerokuに置けるというのは便利

0

preタグを全てgoogle prettyprintする

class=”prettyprint”書くのが面倒だったのでこうなった

<script src="http://shokai.org/js/jquery.min.js"></script>
<script type="text/javascript">
$(function(){
$("pre").addClass("prettyprint").each(function(){
this.innerHTML = this.innerHTML.replace(/<br>/gi, "");
});
$.getScript("http://shokai.org/style/google-code-prettify/run_prettify.js");
});
</script>

0

Node用rocketio-clientを作ったのと、npmの作り方

Sinatra::RocketIOのNode.js用クライアントを作った、という事を書くのを忘れていた。
自作Nodeモジュールをnpmjs.orgへ登録する方法もまとめる。


https://github.com/shokai/node-rocketio-client
https://npmjs.org/package/rocketio-client


感想

npmに初めて登録したけど、rubygems.orgの良い所はそのままにより洗練されてる感じがした。
あとcoffee-script、昔触った時は何これすっげえキモイと思って無理だったんだけど、その頃よりも構文が充実してたし、
Scalaを少し勉強したお陰で目が慣れてて普通に書けるようになってた。

Rubyとscala書ける人はcoffee-scriptすんなり入れると思う。


インストール

npm install rocketio-client

使う


ごく普通にSinatra::RocketIOに接続できる。websocketとcometの使える方が選択される。
RocketIO = require 'rocketio-client'
io = new RocketIO('http://localhost:5000').connect()

io.on 'connect', (io)->
console.log "connect!! (#{io.type})"
io.push 'hello', 'hello world'

io.on 'echo', (data)->
console.log "echo> #{data}"


サーバー側はこんな感じ
main.rb
io = Sinatra::RocketIO

## receive "hello" from client
io.on :hello do |message, client|
puts "> receive '#{message}' from #{client.session} (#{client.type} #{client.address})"

## push "echo" to client
io.push :echo, message
end


自作nodeモジュールをnpmjs.orgに登録する方法

package.jsonを作る

% npm init
対話形式でライブラリの名前やauthor、ライセンス形態を質問されるので答えるとpackage.jsonが生成される

package.jsonを直接編集して、必要あれば項目を追加する。
  • “dependencies” に実行時の依存ライブラリを書く
  • “devDependencies” に開発時に必要なライブラリを書く
  • 実行可能コマンドも配布する場合、 “bin” にpathを書く
  • “main” で指定されたファイルが、require ‘パッケージ名’ された時に読み込まれるファイルになる

もう一度npm initすれば対話形式で再編集できる。


ディレクトリ構成

.
├── History.txt
├── Makefile
├── README.md
├── lib
│   └── rocketio-client.js
├── node_modules

│  (node_modules内は多すぎなので省略)

├── sample
│   └── sample.coffee
├── server
│   ├── Gemfile
│   ├── Gemfile.lock
│   ├── Procfile
│   ├── README.md
│   ├── config.ru
│   ├── lib
│   ├── main.rb
│   ├── npm-debug.log
│   ├── public
│   │   ├── index.js
│   │   └── jquery.min.js
│   └── views
│   └── index.erb
└── src
   └── rocketio-client.coffee


プログラムを書く

他の人のnpmを見ていると、大抵libディレクトリの下にjsを置くようだ。

coffee-scriptを使ってる場合は、lib以外のディレクトリ(src等)を作ってcoffeeはそこに置いて、
都度jsにコンパイルしてlibに書き出す。
Makefileでやった。

サンプルコードを書く

requireはモジュール名やファイル名ではなくディレクトリ名を指定した場合に、そこにpackage.jsonがあれば”main”の項目に指定されたファイルを読み込んでくれる。

なのでsample/sample.coffeeからrocketio-clientをrequireするには
RocketIO = require '../'
と相対パスで書けばいい。


README.mdを書く

markdownでREADME.mdを書くとnpmjs.orgで綺麗に表示してくれる


publish

% npm publish
npmの開発者アカウントを作っていない場合は「adduserしろ」等の指示が表示される。言われたとおりにやる。

% npm adduser
指示に従って入力していけばok

これでもう公開されているので、誰でもインストールしてすぐ使える。


開発中のnpmを公開前にローカルで試す方法

package.jsonのdependenciesにはgitリポジトリを指定できるので、それでインストールできる。

もしくは普通nodeで開発している時はプロジェクトルートにnode_modulesというディレクトリができてその中にnpmがインストールされるので、シンボリックリンクでローカルの開発中のnpmに差し替えるという手もあると思う。

0

Lindaによるブラウザ上での分散処理の例

いわゆる分散処理の例を作ってあってsinatra-rocketio-lindaのsampleの中に入れてあったんだけど、blogに書くのを忘れていたので書きます。
センサーのデータを流して受け取る、いわゆる「pub-sub」的な使い方の他にも、Hadoopがやっているようなタスク分散が簡単に書けるよという例です。


実行環境 http://hello-linda.shokai.org
ソースコード https://github.com/shokai/linda-hello-world


というのも昨日wise9さんに取り上げていただいて、そういえば全然サンプルコードとか整備・整理してない事を思い出したので書こうと思った。


PC遠隔操作編の拡張版みたいなやつです。


動かし方(ごく普通に実行)

clientworkerをそれぞれ開き、「request!」ボタンを押します。
worker側のウィンドウで式がevalで実行されます。

evalなのでclientに「alert(“hoge”)」とか書いてrequestすると、worker側でalertのダイアログがポップアップします。


動かし方(分散実行)

hadoop(map/reduce)がやっているような、仕事を複数のワーカーに分散実行させて結果を一箇所にまとめる処理をブラウザ上のJavaScriptだけで書けます。

まずclientを1つ開きます。
次にworkerを3つ開きます。
clientの「request!」ボタンを押すと、3つのworkerが順番に仕事してくれます。
webブラウザで実行されているので、workerのwebページを手持ちのスマホやタブレットで開けば分散実行に参加できます。



動かし方(仕事を貯めておいて実行)

仕事を貯めておいて、ワーカーが現れるまで待ちます。現れたら一気に実行されます。

まずclientを1つ開き、「requst!」ボタンを連打します。
次にworkerを1つ開くと、今まで溜まっていたrequestが一気に実行されます。



動作原理

3つとも同じコードです。Lindaのtake(削除しつつ読み出し)をうまく使うと、こういう処理が簡単に書けます


Lindaはだれでも読み書きできるタプルスペース(共有メモリ領域)を使った並列処理の実行環境です。

命令はwrite/read/take/watchという4種類しかありません。
writeは書き込み、readはマッチするタプル最新の1個を読み出し、takeはreadしながら削除です。
watchはマッチするタプルがwriteされた瞬間に通知されます。watchはセンサーデータをストリーミングする時などに便利です。

Lindaのタプル読み出しは少し特殊です。
takeとreadは要求しているタプル(データのこと。ArrayかHash)があれば即座に値を読み出しますが、見つからない場合、値が現れるまで待ちます。これが分散実行に使えます。

read/take/watchのマッチングは、「Arrayの前方一致」です。
つまり[“sensor”]は[“sensor”,”light”,20]にマッチするし、[“sensor”,”temperature”, 25]にもマッチします。
[“sensor”,”light”]に範囲を狭めると、[“sensor”,”light”,20]や[“sensor”,”light”, 123]にはマッチしますが、[“sensor”,”temperature”,25]等にはマッチしません。
「Arrayの前方一致」なので、1つ目の要素が”sensor”で2つ目の要素が”light”なタプルにしかマッチしないのです。

タプルの構造によってどんなデータが欲しいかを指定します。


タプルスペースはなんでも入れられるハブです。そこにクライアントをぶら下げるとread/take/watchを駆使してイベント駆動でデータを受け取ると便利だよという事ですね。

ちなみに「だれでも読み書きできる共有メモリ空間」なのですが、Lindaの実装はRubyのwebアプリケーションフレームワーク「Sinatra」のプラグインになっています。
Sinatraでログイン画面とか作れば、特定の人しか書き込みできないようにするとか、ユーザー毎にタプルスペースを分けるとかも可能です。


あとで書く

Lindaでの色々なパターンでのデータの流し方とその例をまとめる
  • pipeline
  • pub-sub
  • req-res
  • job-queue
  • broadcast
  • multisub