http.ServerResponse のフックでログを吐かせる

res.end(...) 書く度に log.write(...) したくない。
というか、自動的にやってほしい。

var path   = require('path')
,   fs     = require('fs')
,   http   = require('http')
,   moment = require('moment')
;

function createSimpleLog (ws, display) {
    ws = ((ws || {}).writable) ? ws : process.stdout;
    return function (req, res) {
        var end = res.end;
        res.end = function () {
            //this.constructor.prototype.end.apply(this, arguments);
            end.apply(this, arguments);

            var now = moment().format("MM/DD/YYYY HH:mm:ss +0900");
            var logMessage = [
                now
              , req.method.toUpperCase()
              , req.url
              , res.statusCode
            ].join(' ');

            ws.write(logMessage + "\n");
            if (display) console.log(logMessage);
        };
    };
}

var log = createSimpleLog(
            fs.createWriteStream(path.join(__dirname, 'access.log'))
          , true);

http.createServer(function (req, res) {
    log(req, res);

    res.writeHead(200);
    res.end(req.url);
}).listen(3000);

Node.jsで複数の 'Set-Cookie' を設定する

メモ。

httpServerResponse.setHeader('Set-Cookie', [
    "KEY1=VAL1; path=/; expires=Sat, 24 Nov 2012 08:34:29 GMT"
  , "KEY2=VAL2; path=/; expires=Sat, 24 Nov 2012 08:34:29 GMT"
  , "KEY3=VAL3; path=/; expires=Sat, 34 Nov 3013 08:34:39 GMT"
]);
httpServerResponse.writeHead(200, {
    'Content-Type': 'text/html'
  , 'Content-Length': Buffer.byteLength(mess)
});
httpServerResponse.end(mes);

もしくは

httpServerResponse.writeHead(200, [
    [ 'Content-Type', 'text/html' ]
  , [ 'Content-Length', Buffer.byteLength(mess) ]
  , [ 'Set-Cookie', "KEY1=VAL1; path=/; expires=Sat, 24 Nov 2012 08:34:29 GMT" ]
  , [ 'Set-Cookie', "KEY2=VAL2; path=/; expires=Sat, 24 Nov 2012 08:34:29 GMT" ]
  , [ 'Set-Cookie', "KEY3=VAL3; path=/; expires=Sat, 24 Nov 2012 08:34:29 GMT" ]
]);
httpServerResponse.end(mes);

httpServerResponse.setHeader('Set-Cookie', "..."); を繰り返すと前出の Set-Cookie の上書きをしてしまうのが微妙です。

おまけ

さっき気づいたけど、querystring.parse 使うとcookieのパースができそう

var qs = require('querystring');
var cookies = qs.parse(httpServerRequest.headers['cookie'].replace(/[,;]\s*/g));

こんな感じ。

readStream.pipe( duplexStream ).pipe(writableStream) してみた

Re:node.jsでストリーミング的な - .blog
これ書いた後に

HTTP_Request.pipe(MyDuplexStream).pipe(HTTP_Response);

させたくなった。
実際には、HTTP_Request.on('data')時のデータをそのまま HTTP_Responseに流すわけじゃないのでデータを変換するストリームの実装になる。

MyDuplexStream の実装

readableStram な
  • this.readable = true する
  • this.emit('data', data) する
  • this.emit('end') する
  • util.inherits(MyDuplexStream, require('stream').Stream) する(ことで、pipeを継承する)
writableStream な
  • this.writable = true する
  • this.write, this.destroy, this.end を実装する
#!/usr/bin/env node
var stream = require('stream')
,   util   = require('util')
,   http   = require('http')
,   path   = require('path')
,   fs     = require('fs')
,   url    = require('url')
;

var config = {
    port : 3000
  , statics : __dirname
};

function FunctorStream (req, res) {
    this.readable = true;
    this.writable = true;

    var rs =
    this.readStream = fs.createReadStream(path.join(
        config.statics
      , decodeURIComponent(url.parse(req.url).pathname)
    ));
    this.writeStream = res;

    rs.on(  'error', this.onError.bind(this));
    rs.once('data',  this.onceData.bind(this));
    rs.on(  'data',  this.onData.bind(this));
    rs.on(  'end',   this.emit.bind(this, 'end'));

    res.on('drain',  this.emit.bind(this, 'drain'));
}
util.inherits(FunctorStream, stream.Stream);
(function (fp) {
    fp.onError = function (err) {
        this.writeStream.writeHead(500, {
            'Content-Type': 'text/plain'
        });
        this.writeStream.end(err.toString());
        console.error(err);
    };
    ('write end').split(' ').forEach(function (writeableMethod) {
        fp[writeableMethod] = function () {}; // dummy
    });
    ('pause resume').split(' ').forEach(function (readableMethod) {
        fp[readableMethod] = function () { this.readStream[readableMethod] };
    });
    fp.destroy = function () {
        this.writable = false;
        this.readable = false;
    };
    fp.onceData = function () {
        this.writeStream.writeHead(200, {
            'Content-Type': 'video/mp4'
          , 'Transfer-Encoding': 'chunked'
        });
    };
    fp.onData = function (chunk) {
        this.emit('data', chunk);
    };
})(FunctorStream.prototype);

http.createServer(function (req, res) {
    req.pipe(new FunctorStream(req, res)).pipe(res);
}).listen(config.port);

console.log('server start to listen on port "%d"', config.port);

現実的じゃないですね

Re:node.jsでストリーミング的な

node.jsでストリーミング的な - 四角革命前夜

インプット(fs.ReadStream)もアウトプット(http.ServerResponse)もStream実装だし、pipe使うのが楽じゃないかと思った。
こんなかんじでどうですかね?

var http = require('http')
,   url  = require('url')
,   path = require('path')
,   fs   = require('fs')
;

var server = http.createServer(function (req, res) {
    var file = path.join( __dirname, url.parse(req.url).pathname );
    var onError = function (e) {
        res.writeHead(500, {'Content-Type': 'text/plain'});
        res.end(e.toString());
        console.error(e);
        return;
    };

    var readStream = fs.createReadStream( file );
    readStream.on('error', onError);

    res.writeHead(200, {'Content-Type': 'video/mp4'});
    readStream.pipe(res);
});

var port = 3000;
server.listen(port);
console.log("server start to listen on port %s", port);

元記事と違って*.mp4 ファイルを ストリームで流すだけのサーバ。
http://127.0.0.1:3000/hoge.mp4 とすると hoge.mp4 をながす



【追記: 2013.02.03】
npm モジュールを使うともっと楽で、例えば filed モジュールを使うと

var http = require('http')
,   url   = require('url')
,   path  = require('path')
,   filed = require('filed')
;

var server = http.createServer(function (req, res) {
    var pathname = url.parse(req.url).pathname;
    filed(path.join( __dirname, pathname)).pipe(res);
});

var port = 3000;
server.listen(port, function () {
    console.log("server start to listen on port %s", port);
});

とするだけで、面倒な背圧の処理や、ETag処理もしてくれる。

関数の書き換え

知らんかったのでメモ

use strict;
use warnings;

sub puts { print "@_\n" }

my @args = qw/Panty and Stocking with Garterbelt/;
puts(@args);

{
    no strict   'refs';
    no warnings 'redefine';

    my $orgin_puts = *{'main::puts'}{CODE};

  # local *main::puts = sub { $oring_puts->(reverse @_) }; #このスコープ内だけ適用なら local つけるといいっぽい
    *main::puts = sub { $orgin_puts->(reverse @_) };

    puts(@args);
}

puts(@args);

結果

Panty and Stocking with Garterbelt
Garterbelt with Stocking and Panty
Garterbelt with Stocking and Panty

知っていて当然の方法なのかな

Niigata.pm 居酒屋LT 2012 秋の陣でのんできました #niigatapm

お酒以外ですけど。

use parent qw{ Hachioji.pm } なLT

正規のLTのような制約がなく、定期開催するには本当によくできたシステムです。
今回参加された方たちには初参加の方(かつ Perlあんまり詳しくないという方)もいらっしゃいましたが、気軽にトークできていたので、ちょっと気になるけど二の足踏んでいるという新潟市周辺のデベロッパーの方たちは顔を出してもらいたいです。
実際にメイン言語に phpRuby、C という人達が来ますし、その言語での作法のような話が聞くことができて刺激になりました。

nodedoc

ishiduca/p5-nodedoc · GitHub

以前のトークの時にでっち上げたコマンドラインツールを作り直して持って行きました。

$ nodedoc module

で、Node.jsモジュールに梱包されているREADME.(md|markdown)を表示するのに作ったんですが、もう少し利用範囲をひろげて

$ nodedoc -m module #モジュール本体のコードを表示
$ nodedoc -l module   #モジュール本体のフルパスを表示
$ nodedoc -ls        # 実行したディレクトリから使用出来るモジュールのリスト表示
$ nodedoc -tree   # 依存モジュールを含めたリストの表示

など。あと、モジュール本体のコードには require('./lib/hoge.js')としか書かれていない場合のために

$ nodedoc -m module -lib ./lib/hoge.js

とすることで、ライブラリのコードを見ることもできます。
また、コマンドラインツールとしての nodedoc は梱包されている Nodedoc.pm を利用しているので、psgi アプリでも利用できます。(デモでは README.md を Text::Markdown で解析しましたが、やっぱりgithub方言のmarkdownには足りません...)

余談ですが、README.* はMarkdown形式が殆んどなので、markdown -> pod で表示したかったんですが、いい具合のモジュールが見当たらなかったので、mad というツールを使うとそれっぽく表示できます。(ただし、github方言のmarkdownには対応しきれていません)

$ nodedoc module | mad -

とすると、多少見やすくなります。

最後に

次回あたり、個人的には TDD な話をぜひ聞きたいです!

Re: node.jsで行処理

これ。node.jsで行処理 - NullPointer's Blog
リンク先の実装は fs.createReadStream のインスタンスのラッパー。
これって fs.createReadStream を拡張した形で実装できないかなと思って実装試しました。結果、**イケてません**。

each-line.js

'use strict';
var fs = require('fs');

function onData (chunk) {
    this.readStream.pause();
    if (this.buf) chunk = this.buf + chunk;
    var that   = this;
    var chunks = chunk.split(this.spliter);
    var last   = chunks.length - 1;
    chunks.forEach(function (line, i) {
        if (i === last) return that.buf = line;
        that.readStream.emit('line', line, that.lineLength);
        that.lineLength++;
    });
    this.readStream.resume();
}
function onEnd () {
    this.buf && this.readStream.emit('line', this.buf, this.lineLength);
    this.buf = '';
    //this.lineLength = 0;
}

function createReadLine (readStream, option) {
    option = (typeof option === 'object' && option !== null) ? option : {};
    if (! readStream) readStream = process.stdin;
    if (typeof readStream === 'string') readStream = fs.createReadStream(readStream, option);
    if (! readStream.readable) throw Error('"readStream" is not "readable"');
    var that = {
        buf: ''
      , lineLength: 0
      , spliter: '\n'
      , readStream: readStream
    };
    option.encoding || readStream.setEncoding('utf8');
    readStream.on('data', onData.bind(that));
    readStream.on('end',  onEnd.bind(that));
    readStream.on('close', function () { that = undefined; });
    return readStream;
}

module.exports.createReadLine = createReadLine;
module.exports.eachline = function (file, onLine, option) {
    var reader = createReadLine(file, option);
    reader.on('line', onLine);
    reader.on('end', reader.destroy.bind(reader));
};

test_read_line.js

var createReadLine = require('./each-line').createReadLine;
var textFile       = 'path/to/sample.txt';
var rl             = createReadLine(textFile);

rl.on('line', function (line, i) {
    console.log('%d: %s', i, line);
});
rl.on('close', function () {
    console.log('CLOSE %s', textFile);
});

単純に一行毎に処理をするだけなら eachline がショートカットっぽいので、それでもいい。

test_eachline.js

var eachline = require('./each-line').eachline;
var textFile = 'path/to/sample.txt';

eachline(textFile, function (line, i) {
    console.log('%d: %s', i, line);
});

`data` イベントの際に読み込んだ `chunk` を splitで配列にしてるところで メモリー食ってるのがイケてません。


追記 (2012.09.30). 第一引数をファイル名ではなく、`readableStream` に変更しました。
例えば、http.example.com/some.txt を行数も表示するなどは

var createReadLine = require('./each-line').createReadLine;
var http = require('http');

var url = 'http://example.com/some.txt';
http.get( url, function (res) {
	var rs = createReadLine(res);
	rs.on('line', function (line, i) {
		console.log('%d: %s', i, line);
	});
});

標準入力を表示するとか

var util = require('util');
var createReadLine = require('./each-line').createReadLine;
var rs = createReadLine(); // createReadLine( process.stdin );
rs.on('line', function (line, i) {
    /^\.exit/.test(line) && rs.destroy();
    util.log(line);
});
rs.on('close', function () {
    console.log('close readline');
});

rs.resume();