サーバのテストをするときなどに未使用のポートを使ってテストコードを走らせたい、というときがあって、PerlだとTest::TCPにempty_portというのがあって簡単に取得出来る。
$ perl -MTest::TCP -E 'say Test::TCP::empty_port' 10256
引数を与えない場合は毎回違う値になるけれど、基本的に必ず空いているポート番号が返ってくる。
nodeでも同じようなのがあればいいな、と思ったのだけど多分ないのでTest::TCPを参考に自分で書いてみた。
exports.empty_port = function(callback) { port = 10000 + Math.floor(Math.random() * 1000); var net = require('net'); var socket = new net.Socket(); var server = new net.Server(); socket.on('error', function(e) { try { server.listen(port, '127.0.0.1'); server.close(); callback(port); } catch(e) { loop(); }; }); function loop() { if (port++ >= 20000) { callback(null); return; } socket.connect(port, '127.0.0.1', function() { socket.destroy(); loop(); }); }; loop(); };
10000〜11000くらいで初期値を決定、net.Socketで127.0.0.1のそのportに繋げるか試し、繋がってしまった場合は既に他のプロセスがlistenしているということなので切って次の番号で再試行。繋がらなかった場合は今度は127.0.0.1のそのportでnet.Serverがlistenできるか試してみて、問題なければcloseした後そのportをcallbackで返す。これが成功するまではport番号をインクリメントさせながら再試行を繰り返す。20000くらいまで試しても見つからなかったら探索を諦めてnullを返すようにしてみた。
Test::TCPのempty_portは同期的に処理を行うようになっているけど、nodeではsocket.connectの際の処理が非同期になるので試行ループをfor文で回して、のようなことができなかった。ので必然的にempty_port関数自体にcallbackを渡して空きportを得る形になる。
$ node -e 'require("./empty_port").empty_port(function(port) { console.log(port) })' undefined 10119
追記
@hide_o_55さんからツッコミをいただきました。ありがとうございます!
[node.js] コールバックの第1引数はエラーオブジェクトを渡したほうがいいかも / nodeで空いているポートを見つける - すぎゃーんメモ URL
exports.empty_port = function(callback) { port = 10000 + Math.floor(Math.random() * 1000); var net = require('net'); var socket = new net.Socket(); var server = new net.Server(); socket.on('error', function(e) { try { server.listen(port, '127.0.0.1'); server.close(); callback(null, port); } catch(e) { loop(); }; }); function loop() { if (port++ >= 20000) { callback(new Error('empty port not found')); return; } socket.connect(port, '127.0.0.1', function() { socket.destroy(); loop(); }); }; loop(); };
callbackに渡す第1引数は正常時はnull, 見つからなければErrorオブジェクト、第2引数に見つかった空きポートを渡すようにしました。