Hatena::ブログ(Diary)

四角革命前夜

2013年02月28日(木)

ltsviewのnode.js実装を書いたよ

no titlenode.jsでの実装を書いてみました。

機能的にはほぼ同じ(JSONでの出力とか、タグの出力ないけど)です。


微妙に違うのは、-f, --fileでファイルを指定したり、-s, --strictで厳密に値のチェックをするくらい。


内部で苦労して実装したpipe使ったら本当に楽だった!

苦労した甲斐があったなーという感じ。pipeって実装者が苦労すれば使う方は楽だね!w

2013年02月26日(火)

参照とか

ちょっとはまった。

function Aaa() {
  this.list = [];
}

Aaa.prototype.test = function() {
  var aaa = this.list;

  this.list = this.list.concat([1,2,3]);

  console.dir(this.list);
  console.dir(aaa);
};

(new Aaa).test();
[ 1, 2, 3 ]
[]

this.listはconcatで生成された新しい配列を参照するようになってて、

aaaはconcatをする前の配列を見てた、ってことであってるのかな。


今更こんなのではまるなんて……orz

2013年02月24日(日)

LTSVにStreamを付けたよ

ltsv-streamから遅れること約2週間、やっとltsvにcreateLtsvToJsonStream関数を追加しました。

Stream手強かったです。まる。


index.js

var ltsv = require('ltsv'),
    ltjs = ltsv.createLtsvToJsonStream();

process.stdin.pipe(ltjs).pipe(process.stdout);

とか書いてあげて、

$ printf 'aaa:bbb\tccc:ddd\neee:fff' | node index.js

とかやってあげれば、出力が

{"aaa":"bbb","ccc":"ddd"}{"eee":"fff"}

になります。やったね!


2週間ほぼStream漬けだったけど、苦労しただけあって得られたものは大きかった気がします。

って対応したのはStream1で、これからStream2が出るのに何やってんだって感じなのですけど。

2013年02月22日(金)

Streamは手強い

https://npmjs.org/package/ltsvがやっとできた……

https://npmjs.org/package/ltsvが出来てから2週間……

もっと精進せねば。ぐぬぬ。

2013年02月17日(日)

続・CountStream

以前作成したCountStreamを再度書き直してみた。

今度のやつはprocess.nextTickで一行ずつdataを発行するようになってます。

counter_stream.js
var stream = require('stream'),
    util = require('util');

function CounterStream() {
  stream.Stream.call(this);

  this.readable = true;
  this.writable = true;

  this.buffer = [];
  this.encoding = '';
  this.paused = false;

  this.count = 0;

  this.ended = false;
  var that = this;
  this.once('pipe', function(src) {
    src.once('end', function() {
      that.ended = true;
    });
  });
}

util.inherits(CounterStream, stream.Stream);

/***/

CounterStream.prototype.destroy = function() {
  var that = this;

  this.readable = false;
  this.writable = false;

  this.once('end', function() {
    that.emit('close');
  });
  this.emit('end');
};

/***/

// data
// end
// error
// close

CounterStream.prototype.setEncoding = function(encoding) {
  this.encoding = encoding;
};

CounterStream.prototype.pause = function() {
  this.paused = true;
};

CounterStream.prototype.resume = function() {
  this.paused = false;
};

/***/

// drain
// error err
// close
// pipe src

CounterStream.prototype.write = function(data, encoding) {
  var that = this,
      str, lines;

  if (!this.writable) {
    this.emit('error', new Error('CounterStream not writable'));
    return false;
  }

  str = (Buffer.isBuffer(data)) ?
      (new Buffer(data)).toString(encoding || this.encoding) : data;

  lines = str.split(/\r?\n/);
  this.buffer.push(
      (this.buffer.pop() || '') + lines.shift());
  this.buffer = this.buffer.concat(lines);

  process.nextTick(function() {
    that.addLineCount();
  });

  return false;
};

CounterStream.prototype.end = function(data, encoding) {
  if (data) {
    this.write(data, encoding);
  }
  this.destroy();
};

CounterStream.prototype.destroySoon = function() {
  this.destroy();
};

/***/

CounterStream.prototype.addLineCount = function() {
  var that = this;

  if (!this.ended) {
    process.nextTick(function() {
      that.addLineCount();
    });
  }

  if (this.paused) {
    return;
  }

  if (this.buffer.length > 0) {
    ++this.count;
    this.emit('data', this.count + ': ' + this.buffer.shift() + '\n');
  } else {
    this.emit('drain');
  }
};

module.exports = {
  create: function() {
    return new CounterStream;
  }
};
index.js
var fs = require('fs'),
    counterStream = require('./counter_stream'),
    c = counterStream.create();

fs.createReadStream('./counter_stream.js').pipe(c).pipe(process.stdout);

for文でとりあえず受け取ったやつは全部処理しちゃうのがいいのか、process.nextTickで分散させた方がいいのか……

あとdataを発行してるけど、pipeでsrc受け取ってwriteするのとは違うのかなあ?どういう風に使い分けるんだろう?

2013年02月16日(土)

はじめてのWritableStream

引き続きStreamのお勉強をしております。今日はWritableStreamを。


wstream.js
var stream = require('stream'),
    util = require('util');

function WStream() {
  var that = this;

  stream.Stream.call(this);

  this.writable = true;
  this.buffer = '';

  process.nextTick(function() {
    that.emit('drain');
  });
}

util.inherits(WStream, stream.Stream);

WStream.prototype.write = function(data, encoding) {
  var str;

  if (!this.writable) {
    this.emit('error', new Error('WStream not writable'));
    this.writable = false;
    return;
  }

  str = (Buffer.isBuffer(data)) ?
    (new Buffer(data)).toString(encoding || this.encoding) :
    data;

  this.buffer = str;
  process.stdout.write(this.buffer);

  var that = this;
  setTimeout(function() {
    that.emit('drain');
  }, 500);

  return false;
};

WStream.prototype.end = function(data, encoding) {
  if (data) {
    this.write(data, encoding);
  }
  this.destroy();
};

WStream.prototype.destroy = function() {
  this.writable = false;
  this.emit('close');
};

WStream.prototype.destroySoon = function() {
  this.destroy();
};

module.exports = {
  create: function() {
    return new WStream;
  }
};

index.js
var fs = require('fs'),
    wstream = require('./wstream'),
    w = wstream.create();

var count = 0;

w.on('drain', function() {
  if (++count < 10) {
    w.write(String(count), 'utf8');
  } else {
    w.end();
  }
});
w.on('error', function(err) {
  console.error(err);
});
w.on('pipe', function(src) {
  console.log('pipe');
});
w.on('close', function() {
  console.log('close');
  w.write('asdf'); // error
});

これを実行すると1..9までカウントした後にcloseと出力してから終了します。


これ、pipeされたときのこと考えるととっても面倒な気がするんだけど……

一筋縄じゃいかないStreamなのでした……

続・WritableStream

pipeしたときのpipeイベントをごにょごにょしなくても、いい感じにやってくれるみたい。

以下はソースコード


wstream.js
var stream = require('stream'),
    util = require('util');

function WStream() {
  var that = this;

  stream.Stream.call(this);

  this.writable = true;
  this.source = null;

  this.once('pipe', function(src) {
    that.source = src;
  });
}

util.inherits(WStream, stream.Stream);

WStream.prototype.write = function(data, encoding) {
  var that = this;

  if (!this.writable) {
    this.emit('error', new Error('WStream not writable'));
    this.writable = false;
    return;
  }

  process.stdout.write(data, encoding);

  setTimeout(function() {
    that.emit('drain');
  }, 500);

  return false;
};
WStream.prototype.end = function(data, encoding) {
  if (data) {
    this.write(data, encoding);
  }
  this.destroy();
};
WStream.prototype.destroy = function() {
  this.writable = false;
  this.emit('close');
};
WStream.prototype.destroySoon = function() {
  this.destroy();
};

module.exports = {
  create: function() {
    return new WStream;
  }
};
index.js
var fs = require('fs'),
    wstream = require('./wstream'),
    w = wstream.create();

fs.createReadStream('./wstream.js', {
  bufferSize: 16
}).pipe(w);

これを実行すると、wstream.jsの中身がちょこちょこ出力されるようになります。

一週間やってみて、まだまだ使いこなせてないけどそれなりにStreamがわかってきた気がする!

2013年02月14日(木)

はじめてのWritable Stream + Readable/Writable(Both?) Stream

昨日に引き続き、Streamを書いてました。ReadableもしくはWritable単体だと理解してるつもりなんだけど、両方を扱うStreamを書いてると、途端に???って感じになる……

そしてWritable Streamのソースコードをなくしたw


Readable/Writable Stream

line_stream.js

LineStreamとか言う名前だけどなにもしないw

var stream = require('stream'),
    util = require('util');

function LineStream() {
  stream.Stream.call(this);

  this.readable = true;
  this.writable = true;

  this.ended = false;
  this.paused = false;

  this.encoding = '';
  this.buffer = '';
}

util.inherits(LineStream, stream.Stream);

/** stream */

LineStream.prototype.destroy = function() {
  this.readable = false;
  this.writable = false;
};

/** readable stream */

LineStream.prototype.setEncoding = function(encoding) {
  this.encoding = encoding;
};

LineStream.prototype.pause = function() {
  this.paused = true;
};

LineStream.prototype.resume = function() {
  this.paused = false;
};

/** writable stream */

LineStream.prototype.write = function(data, encoding) {
  if (this.ended || this.paused) {
    return;
  }

  this.emit('data',
      (Buffer.isBuffer(data)) ? data.toString(encoding) : data);

  return true;
};

LineStream.prototype.end = function(data, encoding) {
  if (data) {
    this.write(data, encoding);
  }
  this.emit('end');
  this.destroy();
  this.ended = true;
};

LineStream.prototype.destroySoon = function() {
  this.destroy();
};

module.exports = {
  create: function() {
    return new LineStream;
  }
};

LineStreamを扱うスクリプトを二種類書いてみたけど、とりあえず両方とも想定通りに動くみたい。

index.js - 1
var linestream = require('./line_stream'),
    l = linestream.create();

require('fs')
  .createReadStream('./line_stream.js', {
    bufferSize: 16
  })
  .pipe(l)
  .pipe(process.stdout);
index.js - 2
var linestream = require('./line_stream'),
    l = linestream.create();

l.on('data', function(data) {
  console.log(data);
});
l.on('end', function() {
  console.log('end');
});

require('fs')
  .createReadStream('./line_stream.js', {
    bufferSize: 16
  })
  .pipe(l);

むむむむ。むずかしい……

CountStream

読み込んだStreamを行ごとに分けて、先頭に行番号を出力するStreamを書いてみた。

count_stream.js
var stream = require('stream'),
    util = require('util');

function CountStream() {
  stream.Stream.call(this);

  this.readable = true;
  this.writable = true;

  this.ended = false;
  this.paused = false;
  this.encoding = '';

  this.count = 0;
  this.buffer = '';
}

util.inherits(CountStream, stream.Stream);

CountStream.prototype.destroy = function() {
  this.readable = false;
  this.writable = false;
};

CountStream.prototype.setEncoding = function(encoding) {
  this.encoding = encoding;
};
CountStream.prototype.pause = function() {
  this.paused = true;
};
CountStream.prototype.resume = function() {
  this.paused = false;
};
CountStream.prototype.write = function(data, encoding) {
  var str, pos;

  if (this.ended) {
    return false;
  }

  str = (Buffer.isBuffer(data)) ?
    data.toString(encoding || this.encoding) : data;

  this.buffer += str;

  if (this.paused) {
    return true;
  }

  pos = this.buffer.indexOf('\n');
  if (pos !== -1) {
    var lines = this.buffer.split('\n');
    for (var i = 0, len = lines.length - 1; i < len; ++i) {
      this.emit('data', ++this.count + ': ' + lines[i] + '\n');
    }
    this.buffer = lines[i];
  }

  return true;
};
CountStream.prototype.end = function(data, encoding) {
  if (data) {
    this.write(data, encoding);
  }
  this.emit('end');
  this.destroy();
  this.ended = true;
};
CountStream.prototype.destroySoon = function() {
  this.destroy();
};


module.exports = {
  create: function() {
    return new CountStream;
  }
};
index.js
var countstream = require('./count_stream'),
    c = countstream.create();

require('fs').createReadStream('./count_stream.js').pipe(c).pipe(process.stdout);

行数を付加して出力は出来たんだけど、for使ってるからなんかこう……writeでブロックしちゃう。

process.nextTick使うと良いのかな?

起動中のプロセスのデバッガを有効にしてデバッグする

node.jsで普通に起動したプロセスを後からデバッガを有効にしてデバッグする方法があるのは知ってたのですが、実際にやったことが無かったので試してみました。


普通に起動させる

とりあえずHTTPサーバを書きます。

index.js
require('http').createServer(function(req, res) {
  res.writeHead(200);
  res.end('Hello');
}).listen(3000);

普通に起動させます。

$ node index.js

デバッガを起動する

$ ps ax | grep node
$ kill -SIGUSR1 (pid)

上で起動させたnodeのpidを調べて、SIGUSR1を投げます。


node-inspectorからデバッグしてみる

$ npm install -g node-inspector
$ node-inspector

あとはGoogle Chromeからhttp://0.0.0.0:8080/debug?port=5858を開くと現在動作しているnodeのコードが表示できるので、ブレークポイントとかを置いてアクセスすると止まります。

peerDependenciesって?

とりあえず

{
  "name": "name",
  "version": "0.0.0",
  "dependencies": {
    "chai": "~1.5"
  },
  "peerDependencies": {
    "chai": "~1.4"
  }
}

って書いて

$ npm install

ってやると怒られるっていうのはわかった。

2013年02月13日(水)

はじめてのReadable Stream

そろそろstream2が出る(というか開発版はもうstream2使えるけど)のにstream1すら全然理解してないので、いろいろ調べつつ書いてみたのです。

いろんなものを読んでも読んでもさっぱりわからない……状態だったのですが、とりあえず実装してみて若干わかったような気がするので一応メモを書いておこうかと。


今回実装したReadable Stream

500ミリ秒置きに数字を出力するもの。ドキュメント通りに実装したつもり……

read.js
var stream = require('stream'),
    util = require('util');

function ReadStream() {
  var that = this;

  stream.Stream.call(this);
  this.readable = true;

  this.encoding = '';
  this.pause = false;

  this.count = 0;
  this.interval = setInterval(function() {
    var buffer;

    if (that.pause) {
      return;
    }

    if (that.count++ < 10) {
      buffer = new Buffer(that.count + '\n');
      that.emit('data',
          (that.encoding) ? buffer.toString(that.encoding) : buffer);
    } else {
      that.emit('end');
      that.readable = false;
      clearInterval(that.interval);
    }
  }, 500);
}

util.inherits(ReadStream, stream.Stream);

ReadStream.prototype.setEncoding = function(encoding) {
  this.encoding = encoding;
};

ReadStream.prototype.pause = function() {
  this.pause = true;
};

ReadStream.prototype.resume = function() {
  this.pause = false;
};

ReadStream.prototype.destroy = function() {
  this.emit('close');
  this.readable = false;
};

module.exports = {
  create: function() {
    return new ReadStream;
  }
};
index.js
var fs = require('fs'),
    readstream = require('./read'),
    rstream = readstream.create();

rstream.pipe(fs.createWriteStream('./out'));

実行してみる

$ node index.js

しばらくするとoutが出力されて、中身には

1
2
3
4
5
6
7
8
9
10

が出力されてます。


ファイルにはちゃんと出力されてたけど、これでいいのかすごく自信ないなあ……

2013年02月10日(日)

LTSVを処理するモジュールを書いたよ

02/08にLTSVについてのことを知って、http://ltsv.org/を見てみたらPerlRubyの実装しかなかったのでnode.jsでの実装を書いてみました。

コードを書きつつ、他のPerlとかRubyの実装どうなってるんだろーと思ってページをリロードしたら、なんか別言語での実装が増えてたりして…… 勢いがすごかったなあ。


0.1.0でparseしか出来なかったので0.3.1までにいろいろ関数を増やしたりとかしました。

連休の良い課題になった気がします。

2013年02月07日(木)

はじめてのレプリケーション

redisでレプリケーションをやったことがなかったのでやってみました。

ちょっと試したいだけならとっても簡単。

環境:OS X 10.7.5 / redis 2.6.9


インストール

適当な場所にインストールします。

$ wget http://redis.googlecode.com/files/redis-2.6.9.tar.gz
$ tar xvfz redis-2.6.9.tar.gz
$ cd redis-2.6.9/
$ PREFIX=$HOME/Binary/redis make install

起動

masterとslaveを起動します。

$ cd bin/
$ ./redis-server &              # slave  : port 6379(default)
$ ./redis-server --port 6380 &  # master : port 6380

slaveの設定をする

slaveとなる方にアクセスし、コマンドを実行します。

$ ./redis-cli --port 6379
> SLAVEOF 127.0.0.1 6380

これでmaster/slaveの設定はおしまい。


試してみる

slave
> set aaa 123
(error) READONLY You can\'t write against a read only slave.
> get aaa
(nil)
master
$ ./redis-cli --port 6380
> set aaa 123
OK
slave
> get aaa
"123"

という風に、masterで設定したものがslaveにも設定されるようになりました。


redisはインストールは簡単で動作も早く、KVSの機能だけでなくpub/subやmaster/slaveの機能などがあって、なかなか面白いです。

httpsのアクセスをnginxでプロキシしてnode.jsに渡す

httpsアクセスを前面に立てたnginxで受け取って、node.jsに渡すというのをやってみました。

かなり面倒そうだなーと思っていたのですが、それほどでもなかったです。

環境:OS X 10.8.2 / node.js 0.8.17 / nginx 1.3.12


証明書の作成

httpsサーバを実行してみたよ - 四角革命前夜を参考にそれっぽく作ります。

$ head -c 20 /dev/random > seed.data
$ openssl genrsa -rand seed.data -des3 1024 > secret-key.pem
$ openssl req -new -key secret-key.pem -out csr.pem
$ openssl x509 -in csr.pem -out server.cert -req -signkey secret-key.pem
$ openssl rsa -in secret-key.pem -out secret-key-nopass.pem

どのファイルがなんなのかとか全然わかってません。


nginxのインストール

まずはnginxをインストールします。

$ curl -O http://nginx.org/download/nginx-1.3.12.tar.gz
$ tar xvfz nginx-1.3.12.tar.gz
$ cd nginx-1.3.12/
$ ./configure --prefix=$HOME/Work/nginx/bin --with-http_ssl_module --without-mail_pop3_module --without-mail_imap_module --without-mail_smtp_module --without-http_rewrite_module
$ make
$ make install

こんな感じでインストールしました。httpsを扱うのでそのモジュールを入れているのと、メール関連のモジュールを外しているのと、PCREを入れるのが面倒だったという理由でrewriteモジュールも外してあります。


nginxの設定ファイルを記述する

次にnginxにプロキシしてもらうため設定ファイルを書きます。

$ cd $HOME/Work/nginx/bin/conf
$ cp nginx.conf{,.bak}
$ vim nginx.conf
nginx.conf
worker_processes  1;

events {
    worker_connections  1024;
}

http {
  server {
    listen 8080;
    server_name localhost;

    ssl on;
    ssl_certificate (パス)/server.cert;
    ssl_certificate_key (パス)/secret-key-nopass.pem;

    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Real-IP $remote_addr;

    location /aaa {
      proxy_pass https://localhost:3000;
    }

    location /bbb {
      proxy_pass https://localhost:3001;
    }
  }
}

鍵の設定は何度か試したらエラーが出なかったのでこれで。


プロキシされるnode.jsのスクリプトを書く

nginxから渡されたアクセスを処理するnode.jsのスクリプトを書きます。

2つ書きますが大してかわりません。

index.js
#!/usr/bin/env node

var fs = require('fs'),
    https = require('https'),
    server;

server = https.createServer({
  key: fs.readFileSync('./secret-key-nopass.pem'),
  cert: fs.readFileSync('./server.cert')
}, function(req, res) {
  res.writeHead(200);
  res.end('/aaa');
}).listen(3000, function() {
  console.log('server running port at 3000');
});
index2.js
#!/usr/bin/env node

var fs = require('fs'),
    https = require('https'),
    server;

server = https.createServer({
  key: fs.readFileSync('./secret-key-nopass.pem'),
  cert: fs.readFileSync('./server.cert')
}, function(req, res) {
  res.writeHead(200);
  res.end('/bbb');
}).listen(3001, function() {
  console.log('server running port at 3001');
});

nginxとnode.jsを起動させる

あとは起動させてアクセスするだけ!

$ ./nginx
$ node index.js &
$ node index2.js &

これでFirefoxからhttps://localhost:8080/aaahttps://localhost:8080/bbbにアクセスするとそれぞれの出力が表示されます。


nginxのことをまだまだ全然知らないので、少しでも慣れるように使っていくか、node-http-proxyでプロキシを書いてしまうか……

2013年02月02日(土)

1月のまとめ

2013年明けたと思ったら、もう1ヶ月経つとか早いねえ……

この調子でどんどん歳を取っていくのかと思うと、のんびりしてられないような気がする。


やったこと

  • hatena.vimを試した
  • zを試した
  • MacBookSnow LeopardからMountain Lionに
  • Server-Sent Eventsを試した
  • tigを試した
  • mktempを書いた
  • deepcopyを書いた
  • その他node.js関連のことをいくつか試した

やったことは今まで通り大してかわらないかも。

でもmktempとdeepcopyを書いて公開したので、小さいながらもモノを作れてると思う。

この調子で段々と規模の大きいものを作っていきたいかなあ。

2013年02月01日(金)

C-dをtrapする

trap 'echo exit' EXIT

とか書けばいいみたい。

trap 'eval `/usr/bin/ssh-agent -k`' EXIT

とか書いたらtmuxでC-d押すたびに発動するから逆に困る事態に……

bash_logoutに書きなおしたけど。

Connection: close