nginxでsocket.ioのリバースプロキシ設定
今更だけどもnginxをリバプロに使ってsocket.ioを動かす方法を覚書。
nginx_tcp_proxy_module
If you’re using nginx, you won’t be able to proxy web socket connections using the standard nginx proxy_pass directives.
Fortunately, Weibin Yao has developed a tcp proxy module for nginx that allows you to reverse proxy general tcp connections, especially well suited for websockets.
http://www.letseehere.com/reverse-proxy-web-sockets
上の記事やgithubのsocket.ioのwikiにも書いてありますが、どうもnginxは最新安定版の1.0ではhttp 1.1のupgradeをサポートしておらず、またnginx1.1ではhttp1.1のupgradeはサポートしているものの、websocketをサポートしていないために、socket.ioのプロキシが上手くいかないようです。
(確かsocket.ioは最初xhrで接続して、クライアントがwebsocket使えたらwebsocketにupgradeするという流れだった気がするけどソース忘れた)
まぁそんなわけで、現時点では素のnginxでsocket.ioのリバースプロキシは行えないということです。そこで上の引用にもあるように、「nginx_tcp_proxy_module」というモジュールを組み込んでこの問題を解決します。
前回記事にも書いたように、nginxの追加モジュールはソールコンパイル時にオプションとして指定しなければなりません。
nginxのソースと共にモジュールをgithub(https://github.com/yaoweibin/nginx_tcp_proxy_module)から持ってきて、パッチをあててコンパイルします。
$ wget http://nginx.org/download/nginx-1.0.11.tar.gz $ tar zxvf nginx-1.0.11.tar.gz $ cd nginx-1.0.11 $ git clone https://github.com/yaoweibin/nginx_tcp_proxy_module.git $ patch -p1< nginx_tcp_proxy_module/tcp.patch $ ./configure --add-module=./nginx_tcp_proxy_module $ make $ make install
その他のコンパイルオプションなどについては前回記事を参照してください。
モジュールの設定方法
このモジュールをインストールすると、設定ファイル内で新たにtcpブロックが使えるようになります。このブロック内にsocket.io向けの設定を書いていくことになります。
tcp { upstream websockets { ## node processes server 127.0.0.1:8001; server 127.0.0.1:8002; server 127.0.0.1:8003; server 127.0.0.1:8004; check interval=3000 rise=2 fall=5 timeout=1000; } server { listen 127.0.0.1:80; server_name _; tcp_nodelay on; proxy_pass websockets; } } http { ## status check page for websockets server { listen 9000; location /websocket_status { check_status; } } }
上記の設定はhttp://www.letseehere.com/reverse-proxy-web-socketsのをそのままもってきました。
基本的には設定はhttpブロックを使うときとそんなに変わりません。tcpブロック内のserverブロックでホスト名やlistenポートを指定し、proxy_passでリバースプロキシの設定を行います。あとはupstreamブロック内にアクセスを振り分けるサーバを指定すれば終わりです。checkディレクティブはヘルスチェックの設定を行います。
ちなみに上記の設定でhttpブロック内に check_status というディレクティブがありますが、これをhttpブロック内で指定すると、upstreamに書かれているサーバへのヘルスチェックの簡易確認ページを見ることができます。以下のような感じです。
他のディレクティブなどの説明に従いてはhttps://github.com/yaoweibin/nginx_tcp_proxy_module
では適当につないでみます。
サーバ側コード
var http = require('http'), io = require('socket.io'); var server = http.createServer(function(req, res){ res.writeHead(200, {'Content-Type': 'text/html'}); res.end('Hello world'); }); server.listen(8008); // socket.io var socket = io.listen(server); socket.on('connection', function(client){ console.log('client has connected'); });
ポートが8008なのは僕の家の環境のせいなので適当に読み替えてください。これを対象のサーバで実行すると
上手く行ってるみたいですね。これで完了です。
chromeデベロッパーツールでjavascriptのデバッグをする -node.jsもあるでよ-
ソースコードリーディングとかしてると、ただコード読んでてもどうしようもなく、オブジェクトの中身や変数などを見るためにデバッグツールを使いながらでないとやっていけないことが今になって分かりました。自分でもどうしようもなくアホだと思いながら戒めのために覚書。
デバッグツールの機能
僕自身まともに触れる言語はjavascriptとphpくらいなもので、どちらもeclipseのようなIDEを使わず頑なにvimを使って組んできました。phpの場合はxdebugと連携させる方法*1や、javascriptならrhinoなんかを入れてquickrunとかって方法も考えられますが、僕はある程度は知っていながらもひたすら標準のスタックトレースやalert,console.log,console.dirばかりしていたので、まずはIDEなどに搭載されている一般的なデバッグ機能を復習をかねて覚書。
ブレークポイント
ブレークポイント(英: breakpoint)は、ソフトウェア開発のデバッグ作業において実行中のプログラムを意図的に一時停止させる箇所である。ブレークポイントの指定機能は多くのデバッガに備えられており、これを用いることでプログラムの任意箇所への到達を自動的に捕捉できる。ブレークポイントでの停止後、プログラマは通常のデバッグ作業同様に実行環境(メモリ、レジスタ、ログ、ファイルなど)を観察し、プログラムが期待通りに機能しているかどうかを判断する。
との事。自分の期待しない動作をするときなど、一時停止してそのときの変数やプログラムの状態を確認。
どうでもいいですが、なんでブレークポイントを「張る(貼る?)」という表現するんでしょうね。
ステップイン、ステップオーバー、ステップアウト
デバッグ時はプログラムを1行または複数行ごとに手動で実行することができます。プログラムの最初から1行ずつというのではなく、ブレークポイントを張った場所で一旦停止し、それから各単位での実行になります。
その実行単位は以下の3つのような呼び方をされます。
- ステップイン
- 1行ごとに実行する。
- ステップオーバー
- 1行ごとに実行する。ただし関数があった場合はそれを1行とみなして実行する。
- ステップアウト
- 実行している関数が元に戻るまで実行する。
以下のようなコードを考えます。
function increment(arg){ var increment = 1, ret; // set breakpoint of StepOut ret = arg + increment; return ret; } var number = increment(1); // set breakpoint of StepIn,StepOver alert(number);
numberという変数の宣言をしている行にブレークポイントを設定したとします。ここでステップインをすると、1行ごとの実行を行うために次に実行されるのはincrementという関数内のincrementとretの変数宣言部になります。
ステップオーバーの場合は関数の内部に入らないため、次のalertが実行されることになります。
また、関数increment内でブレークポイントをはっている場合、ここでステップアウトを行うと、1行ずつの実行はせずに関数の最後まで実行するため、これまた次のalertが実行されます。
chromeデベロッパーツール
こっからが本題ですが、chromeのデバッグツールを使いこなそうというわけです。
windowsまたはlinuxの場合はCtrl+Shift+i、オシャレ気取りども御用達のmacの場合はCommand+Option+iで起動します。またページ内を適当に右クリックして「要素を検証」を押してもデベロッパーツールは起動します。
パネルがたくさんあると思うのですが、今回主に使うのは[Scripts]パネルなので、それについてだけ説明。
左ペインに解析中のスクリプトが表示されます。1の「test.js」と表示されている部分をクリックすると、同じくそのページで読み込まれているスクリプトを選ぶことができます。
スクリプトの左端に行数が表示されていますが、行数をつぶして赤い矢印の出ている部分が今実行した行です。また青い矢印は設定されたブレークポイントをさします。
設定したい行を左クリックでブレークポイントが設定でき、再度クリックすればブレークポイントをはずすことができます。また、右クリックで条件付ブレークポイントの設定もできます。
右ペインは実行時の状況が表示されます。
一番上にはステップ実行のコマンドボタンが表示されます。以下のボタンのように割り当てられています。
Watch Expressions
「WatchExpressions」は任意の式や変数を入力しておけば、その内容が確認できます。+ボタンを押せば入力ボックスが出てくるので、その中に変数名などを入力します。例えば「this」を入れておけばステップ実行時に各行でthisは何にバインドされているか確認することができます。thisが面倒なjavascriptでこれはすごく便利。
Call Stack
その名の通りコールスタックが見えます。関数内でさらに関数やメソッドを呼んだ場合に、スタックに実行途中の関数などの情報が積まれます。それらが全て見えるようになるわけです。表示されている各関数をクリックすれば、その関数内におけるScopeVariables(後述)やwatchExpressionで設定したthisの値が確認できます。
Scope Variables
実行行からアクセスできる変数スコープが見えます。グローバルスコープ(windowオブジェクト)やローカルスコープ、またクロージャを保持していればクロージャの中も見えます。まる見えです。
ブレークポイント
Breakpoints以下は設定されているブレークポイントが見れます。
「Breakpoint」には上述の方法で設定したブレークポイントをどこにはったかという情報が見えます。
で、僕が感動したのはここからの機能です。
手動では設定できないような面倒な部分のブレークポイント設定がサポートされているのです。
DOM Breakpoints
ページに表示されるある要素を右クリックし、「要素を検証」を押します。そうするとデベロッパーツールの[Elementsタブ]が開かれ、さっきクリックした要素のHTMLがハイライトされます。ここでさらにハイライトされている部分を右クリックすると
- Break on Subtree modifications
- Break on Attribute modifications
- Break on node removal
という選択肢が表示されます。
これらを選ぶことで、その要素ツリーや属性の変更時・ノード削除時にブレークポイントを設定することができるわけです。これはなかなかすごい。
実際に試してみるのが早いのでしょうが、はてダじゃサンプルスクリプトを実行できないので、chromeデベロッパーツール公式ページで試してみましょう。
XHR Breakpoints
XHRでsendを使った瞬間にブレークさせることができます。これもchromeデベロッパーツール公式ページで試してみてください。
「XHR Breakpoints」の横にある+ボタンを押してurlを入力すると、そのURLと通信した際にブレークします。
Event Listener Breakpoints
イベントのタイプが並んでいるのですが、チェックを入れたイベントが発生した際にブレークします。
これもchromeデベロッパーツール公式ページで試すと良いです。
console
ちょっとブレークポイントを離れてコンソールを動かしてみましょう。
[Console]タブでコンソールを動かすこともできるのですが、デベロッパーツール下部の変なボタンを押してもコンソールを出すことができます。後者の場合は[script]タブと同時に表示できるのでおすすめです。
コンソールはブレークしてスクリプトを停止している最中でも値の代入か関数の実行を行えます。補完機能が働くのですごく使いやすいです。
また以下のような特殊関数をコンソール内では使用できます。
$ document.getElementByIdのショートカット $$ document.querySelectorAllのショートカット $x document.evaluate(XPath)のショートカット $1, $2, $3, $4 $1から$4にはコンソールでの実行結果が4回分だけ記憶されています。 copy(text) 引数に渡した文字列をクリップボードにコピー dir(object) 引数に渡したオブジェクトを解析(DOM要素を渡したときもオブジェクトとして扱う) dirxml 引数に渡したノードをツリー表示 inspect 引数に渡したオブジェクトに応じて適切に解析を行う(localStorageを渡すとStorageパネルに切り替えるなどの処理も行う)。 keys オブジェクトのプロパティを配列で返す monitorEvents 引数に渡したDOMについて各種イベントを監視する。第2引数で監視するイベントの種類を制御できる。 unmonitorEvents monitorEventsを解除 profile JavaScriptのプロファイリングを開始する profileEnd JavaScriptのプロファイリングを終了する values オブジェクトが持つ値を配列で返す
参考:http://dl.dropbox.com/u/148989/slide/console.html
[http://code.google.com/intl/ja/chrome/devtools/docs/overview.html:title=http://code.google.com/intl/ja/chrome/devtools/docs/overview.html
]
組み込みデバッガを使う
node.jsは特にモジュールを追加することもなく、最初から組み込みのデバッガが入っています。基本コンソールからの操作になりますが、それでもブレークポイントはったりステップ実行したりと基本的なことはできるようです。
以下のようにdebug引数を指定してnodeを起動するとデバッグクライアントが起動します。
$ node debug test.js
これでデバッグプロンプトが表示されるようになり、デバッグ用コマンドの入力ができるようになります。
デバッグ用のコマンドについてはデバッグプロンプトでhelpと入力したり、公式のドキュメントで確認できます。
node-inspector
でもやっぱりCUIでのデバッグは見づらいし分かり難くて面倒なわけです。
その面倒さを解決してくれるのがnode-inspectorなのです。
node.jsもchromeもjavascriptエンジンはV8ですし、chromeのデベロッパーツールの見た目はjavascriptとcssで作られているようなので、それをうまいこと組み合わせて、上で散々紹介したデベロッパーツールでnode.jsのデバッグも行えるようにしたのがnode-inspectorなのです。これこそが僕がchromeをjavascriptのデバッグに使いたかった理由です。*2
というわけでnode-inspectorを使ってみましょう。
まずはnpmでインストールします。
$ npm install -g node-inspector
インストールができたらnode-inspectorをバックグラウンド起動させます。
$ node-inspector &
visit http://0.0.0.0:8080/debug?port=5858 to start debugging
これでvisit云々と書いてあるアドレスにchromeでアクセスすればnode-inspectorが動いているのが分かります。
次に適当にテスト用のコード(test.jsとします)を作ってみます。
var http = require('http'); http.createServer(function (req, res) { console.log("1"); res.writeHead(200, {'Content-Type': 'text/plain'}); req.on("end", function(){ console.log("2"); console.log("3"); res.write("debugging node-inspector"); res.end(); }); }).listen(1337, "127.0.0.1"); console.log('Server running at http://127.0.0.1:1337/');
これをデバッグモードで動かしてみましょう。
$ node --debug test.js
さっき動かしたnode-inspectorの画面に行って[Script]タブを選択し、test.jsを表示させましょう。[Script]タブを開いたときはhttp.jsなどの組み込みモジュールが表示されているので、スクリプト名の表示されているボタンを押してtest.jsを選択します。
test.jsが表示できたら、好きなところでブレークポイントを設定します。console.logにでも設定してみましょう。
ブレークポイントを設定した状態で、新たにタブを開いてtest.jsがlistenしているhttp://127.0.0.1:1337/にアクセスしてみます。
アクセスした段階では debugging node-inspector という文字は表示されていません。
これはさっきはったブレークポイントで一時停止しているためです。元々開いていたnode-inspectorのタブに戻ると、test.jsのステップ実行ができる状態になっているはずです。
参考:
https://github.com/dannycoates/node-inspector
http://d.hatena.ne.jp/replication/20111202/1322752174
node.js入門と自分的勘所 -nvmインストールからhello worldまで-
一ヶ月ほど前に少しだけ入門して、昨日から再入門したらいろいろ忘れてたし、クライアントサイドJSにはなかった慣れない部分を調べてみたので覚書。
node.jsでアプリを作ってみる
まずはnvmをインストールします。nvmはnode.jsのバージョン管理システムです。
node.jsはものすごい勢いで開発が進むことから、情報が陳腐化しやすいです。あるバージョンのnodeで動くアプリを作っても、数ヶ月後にその時の最新バージョンで試して動かないことも多々あるでしょう。そのため、node自身のバージョン管理、どのバージョンを有効にするかなどの管理が容易にできるのがこのnvmです。
$ git clone git://github.com/creationix/nvm.git .nvm
で.nvmディレクトリを作成し、その中にcloneします。
$ . .nvm.nvm.sh
でインストール完了。
あとはこのnvmを使って最新版のnode.jsをいれます。今現在の最新版は0.6.6です。
コマンドの引数にインストールしたいバージョンを入れれば、そのバージョンのものがインストール出来ます。
$ nvm install v0.6.6
インストールができたら、次回の起動時以降勝手にnvmを立ち上げるよう.bashrcに設定を追加します。
$ vim ~/.bashrc
. ~/.nvm/nvm.sh
nvm use "v0.6.6"
nvm,node.jsがインストールできたらnpmというパッケージ管理システムを使って適当にパッケージをインストールしてみましょう。npmは今はnode.jsに標準装備されているので、特別に何かが必要なことはありません。
$ npm install socket.io
socket.io@0.8.7 ./node_modules/socket.io
├── policyfile@0.0.4
├── redis@0.6.7
└── socket.io-client@0.8.7
上記のコマンドを打ち込んだディレクトリ内にnode_modulesというディレクトリが作成され、その中にモジュールがインストールされます。
- gオプションを付けることで、.nvmディレクトリ以下にモジュールがインストールされ、しかもそのインストール先にパスが通るようになります。
$ npm install express -g
あとは何か適当に作ってみます。とりあえずExpressをつかってみましょう。Hello World表示するだけなアプリです。
var express = require("express"), app = express.createServer(), port = process.env.PORT || 3000; app.configure(function(){ app.use(app.router); }); app.get("/", function(req, res){ res.end("hello world!!"); }); app.listen(port);
ファイル名をapp.jsとして作成できたら、以下のように立ち上げます。あとはhttp://localhost:3000/にアクセスすれば「hello world!!」が表示されるはずです。
$ node app.js
node.jsの僕的勘所
自分的に引っかかったところ、慣れないところなど。
EventEmitter
node.jsがイベント駆動バリバリなのはどこでも言われてることだと思います。
登場するオブジェクトがほとんどonとかemitというイベントハンドラを持っていて、これはDOMで言うところの、HTMLElementsが持ってるaddEventListenerみたいなもんなのかと思いきや、組み込みのhttpとかfsモジュールのインスタンスはonとかemitとかって名前ではないイベントハンドラもっているので、なんか統一性なくて気持ち悪いなと思っていたのです。
で、よくよく調べてみたところ、このonとかemitとかっていうハンドラ以前に、イベントを生成するオブジェクトは全て組み込みのevents.EventEmitterのインスタンスだとのことです。なのでhttpやfsが持つイベントハンドラなどは、そevents.EventEmitterのメソッドをラップしてるもののようでした。
自分で独自のイベントを作成するモジュールを作る際も、このインスタンスを使用することになります。
参考:http://dl.dropbox.com/u/219436/node.js/handson/build/html/intro/programming_model.html
モジュール
クライアントサイドjsと違うポイントの1つにモジュールの読み込みができることがあります。
モジュールはrequireメソッドで読み込みが出来ます。
/** * counter module */ var counter = 0; export.count = function(){ console.log("counter: " + counter); counter++; }
上記のようなモジュールを考えます。モジュール内ではexportsというオブジェクトを自動で用意します。このオブジェクトはrequireの戻り値になります。なので、このexportsにメソッドなど追加すれば、読み込み側でもそれが使えるようになるわけです。逆にexportsに追加しなかったものは読み込み側からはアクセスできません。この場合、countメソッドを呼び出してcounterの値をインクリメントできますが、直接counterという変数にアクセスはできません。
また、自作のモジュールを以下のように呼びだそうとしたときに失敗しました。
require("counter");
呼び出し側とモジュール自体は同じディレクトリにあり、counter.jsというモジュールの名前も間違っていません。これも色々調べてみたところ、モジュールの読み込みルールは以下のようになっているとのことでした。
require() では常にコアモジュールの識別名を優先的に解釈します。 例えば require('http') は、例え同名のファイルが存在していたとしても、常にビルトイインの HTTP >モジュールを返します。
指定された名前のファイルが見つからなかったら、 Node は指定されたファイル名に .js、.json、または .node を付けたものを読み込もうとします。
.js ファイルは JavaScript ファイルとして解釈され、 .json ファイルは JSON ファイルとして解釈されます。 一方 .node ファイルはコンパイル済みのアドオンモジュールとして解釈され、 dlopen を使って読み込まれます。
'/' から始まるモジュールは、ファイルへの絶対パスと見なされます。 例えば、 require('/home/marco/foo.js') は /home/macro/foo.js を読み込みます。
'./' から始まるモジュールは、 require() を呼んだファイルからの相対パスになります。 すなわち、 foo.js から require('./circle') によって circle.js を読み込むには、 circle.js は foo.js と同じディレクトリに存在していなければなりません。
'/' や './' が先頭になければ、モジュールは "コアモジュール" であるかもしくは node_modules フォルダから読み込まれることになります。
もし require() に渡されたモジュール識別子がネイティブモジュールではなく、かつ '/' や '../' や './' から始まらないならば、 Node は現在のモジュールの親ディレクトリに '/node_modules' を付与してそこからモジュールを読み込もうとします。
そこに見つからない場合はさらに親ディレクトリに移動し、モジュールが見つかるか root ディレクトリに到達するまで同様のことを繰り返していきます。
例えば '/home/ry/projects/foo.js' の中で require('bar.js') を呼んでいた場合、 Node は下記の位置を上から順番に見ていきます。
僕が試した方法だと./がないために行けなかった模様。少しはまりました。