小林 (koichik) です. > ただ isaacs が書いているよう http.ServerRequest 等の場合は、response イ > ベントが既に fd からデータバッファを読み込んでいる後なので pause() でも > きっちり止まらず、data が漏れ出てきちゃう可能性があります。 なので注意が > 必要です。
http の場合、v0.6 までは止まるとは限りませんが、 v0.8 の場合はきっちり確実に止まります。 IncomingMessage 自体がバッファリングして、pause() 中は 'data' イベントを emit() しないからです (fs.ReadStream も同様)。 Streams1 の問題点の一つは、上記のように各ストリームが 個別にバッファリングしていたりしていなかったり、 バラバラに対応していたことでしょうね。 とはいえ、v0.8 ではバッファリングしない (pause で 止まるとは限らない) ストリームは結局 net.Socket だけに なってた気がしますけどw > fs.stat のエラーをひっかけて 404 の処理をする方が効率的だと思います。 あう、そこを指摘すると課題 1 が成立しなくなって あうあうあー On Fri, 19 Apr 2013 10:34:01 +0900, Shigeki Ohtsu <oh...@iij.ad.jp> wrote: > 大津です。 > > # 初心者スレなのに続けてしまってすみまへん > > こいうい問題があって stream2 へと続いていくんですが、これに関しては東京 > Node学園祭の isaacs の講演資料 > > https://dl.dropboxusercontent.com/u/3685/presentations/streams2/streams2-nodefest.pdf > > の p26 「immediate 'data'」のスライドを参照されるとよいかと思います。 > > 続く p27 に 「pause() でもダメだ!」 と書いてありますが、 > fs.fs.ReadStream の場合は nextTick で read(2) するのできっちり止まりま > す。(さすが koichik さん!) > > ただ isaacs が書いているよう http.ServerRequest 等の場合は、response イ > ベントが既に fd からデータバッファを読み込んでいる後なので pause() でも > きっちり止まらず、data が漏れ出てきちゃう可能性があります。 なので注意が > 必要です。 > > で、先の返事でもあったように promise っぽくキャッシュ生成後にレスポンス > を返すと巨大ファイルだとキャッシュ生成時間分待たされるので、できれば > キャッシュ生成しながら、追っかけ再生みたいにキャッシュバッファを吐き出せ > ればいいんですが、チト簡単に思いつきませんでした。 > でも、これで来月のハッカソンのお題が見つかったのでラッキーです。 > > あと一つサンプルコードに注文を付けさせていただくと、最初 fs.exists() で > ファイルの存在をチェックしていますが、これ中身は fs.stat() なので結局2 > 回 stat(2) を行うことになり無駄です。 > fs.stat のエラーをひっかけて 404 の処理をする方が効率的だと思います。 こ > こで Domains の登場ですが、どう使うかは「Node.js入門」をご覧くださいw > (ステマ) > > (2013/04/19 8:54), yuichiro wada wrote: > > 小林さん > > > > ありがとうございます。 > > 恥ずかしながら見えていませんでした。 > > # 大津さんが仰っていたのもこれだったのですね。 > > > > 取り急ぎお礼まで。 > > > > > > -- > > yuichirow...@gmail.com <mailto:yuichirow...@gmail.com> (pc/mobile) > > mobile 090-1736-8716 > > @yuichirowada on twitter > > > > > > > > 2013/4/19 Koichi Kobayashi <koic...@improvement.jp > > <mailto:koic...@improvement.jp>> > > > > 小林 (koichik) です. > > > > ともあれ (JW)、一つ目の課題について自分が > > 想定していた問題点を書いてみます。 > > > > サンプルのコードは以下の構造を持っています。 > > > > var s = fs.createReadStream(f).once('open', function () { > > this.pipe(response); > > }); > > fs.stat(f, function(err, stats) { > > s.on('data', function(data) { > > }); > > }); > > > > pipe() が使われていますが、その中では 'data' > > (v0.10 以降では 'readable') イベントのリスナが > > 登録されるので、それを表に出すと以下のように > > なります。 > > > > var s = fs.createReadStream(f).once('open', function () { > > //(1) > > s.on('data', function(data) { > > //(2) > > }); > > }); > > fs.stat(f, function(err, stats) { > > //(3) > > s.on('data', function(data) { > > //(4) > > }); > > }); > > > > ここで、(1)->(2) および (3)->(4) はこの順序で > > 実行されますが、それぞれの間では順序の保証は > > ありません。 > > > > そのため、もし fs.stat() に時間がかかると、 > > (3) のところで 'data' リスナを登録するよりも > > 先に、最初の 'data' イベントが発生してしまう > > 可能性があります。 > > > > その場合、始めの方のデータは (2) の 'data' > > リスナだけが受信し、(4) の 'data' リスナは > > それを取りこぼすことになってしまいます。 > > > > これは現実的にはまず起きないとは思いますが、 > > 似た状況は setTimeout() を作ることができます。 > > > > var s = fs.createReadStream(f).once('open', function () { > > this.pipe(response); > > }); > > setTimeout(function() { > > fs.stat(f, function(err, stats) { > > s.on('data', function(data) { > > }); > > }); > > }, 1000); //時間は適当 > > > > > > これが一つ目の課題として想定した問題点です。 > > > > > > 回避策としては、pause()/resume() を使うのが > > 素直なやり方かと思います。 > > > > var s = fs.createReadStream(f).once('open', function () { > > this.pipe(response); > > }); > > s.pause(); //一時停止 > > fs.stat(f, function(err, stats) { > > s.resume(); //再開 > > s.on('data', function(data) { > > }); > > }); > > > > v0.8 以前の場合、pause() は「アドバイス的」な > > ものということになっていますが、fs.ReadStream は > > かなり初期から (たぶん v0.2 の頃から) 中断中は > > 'data' イベントを生成せずにバッファリングしてくれて > > いたのでこれで大丈夫。 > > > > v0.10 以降で導入された Stream2 では pause() は > > 「アドバイス的」ではなくなったのでやっぱり > > 大丈夫、そして pause() を呼び出すと "oldMode" に > > 切り替わるので、後から 'data' リスナを追加しても > > 安心になります。 > > > > > > > > On Wed, 17 Apr 2013 02:40:00 +0900, Koichi Kobayashi > > <koic...@improvement.jp <mailto:koic...@improvement.jp>> wrote: > > > > > 小林 (koichik) です. > > > > > > なぜ初心者には見えない人達が盛り上がってるのか > > > 小一時間(ry > > > > > > ともあれ (JW)、翻訳書からのサンプルなので、 > > > 時期的に原書が v0.10 に対応しているはずがなく、 > > > ここでは Stream1 の話ということにした方が > > > いいんじゃないかと思いますけれど。 > > > > > > > てことで、「fs.stat() も open イベント内部に押し込むと、意図し > > た順序で処理してくれる。」てのが課題に対する私の解答。 > > > > > > んー、自分が想定 (期待) したのと違う問題を > > > 解決しようとしてるようなw > > > > > > fs.stat() を 'open' イベントリスナの中に押し込むと、 > > > fs.stat() のコールバックが呼び出されるタイミングは > > > 元のコードよりさらに遅くなりますよね。 > > > > > > すると、キャッシュのバッファを埋めるための 'data' > > > イベントのリスナを登録するのもさらに遅くなりますね。 > > > それで大丈夫でしょうか? > > > > > > たとえば極端な例として、 > > > > > > var s = fs.createReadStream(f).once('open', function() { > > > response.writeHead(200, headers); > > > this.pipe(response); > > > }); > > > > > > setTimeout(function() { > > > s.on('data', function(buf) {...}); > > > }, 10 * 1000); // 10 秒!!!! > > > > > > だったらどうでしょうか? > > > setTimeout() の中で登録した 'data' リスナは > > > 適切に呼び出されるでしょうか? > > > > > > > > > On Tue, 16 Apr 2013 08:11:48 +0900, Akitoshi Manabe > > <akitoshi.man...@gmail.com <mailto:akitoshi.man...@gmail.com>> wrote: > > > > > > > 眞鍋です。 > > > > > > > > スミマセン。誤解してました。 > > > > 以下のファイルを書いて試したところ、fs.createReadStream だと > > > > readable イベントは発火しませんでした。 > > > > > > > > ---- (ここから) > > > > var util = require('util'); > > > > var fs = require('fs'); > > > > var f = __filename; > > > > > > > > var s = fs.createReadStream(f, {bufferSize: 128 }); // default > > : 64 * 1024 > > > > [byte] > > > > s.on( 'readable', function(){ util.puts( 'readable', arguments > > ) } );//発火せず > > > > s.on( 'open', function(){ > > > > util.puts( 'open', arguments ) > > > > // data イベントはopen後でも追加できる。 > > > > s.on('data', function( d ){ util.puts('DATA ', d) }) > > > > } ); > > > > s.on( 'error', function(){ util.puts( 'error', arguments ) } ); > > > > s.on( 'close', function(){ util.puts( 'close', arguments ) } ); > > > > ---- (ここまで) > > > > > > > > 実は、readable イベントに押し込めるのではないかと考えていましたが、 > > > > 「data イベントは open 直後に実行されるハンドラ内に定義しても良 > > い」と確認できます。 > > > > てことで、「fs.stat() も open イベント内部に押し込むと、意図し > > た順序で処理してくれる。」てのが課題に対する私の解答。 > > > > > > > > > > > > Nodeを始めた頃「関数のネスト(ピラミッドとも)で書く特徴」に悩 > > まされました。 > > > > 理解が深まってくると、ピラミッドをフラットで見やすく書く為の > > > > 「フロー制御」を考えたくなると思います。 > > > > > > > > > > > > > > > > > > > > 2013年4月16日 7:08 aporo4000 <hiroshi4...@gmail.com > > <mailto:hiroshi4...@gmail.com>>: > > > > > > > > > 全然違うかもしれませんが、最初に読み込むバイト数を指定すると > > か!?ですか? > > > > > > > > > > 2013年4月16日火曜日 1時20分00秒 UTC+9 koichik: > > > > >> > > > > >> 小林 (koichik) です. > > > > >> > > > > >> 勝手に課題シリーズ第二弾w > > > > >> 前のメールでも書いたように、非同期 API では > > > > >> > > > > >> ... //(1) > > > > >> foo(hoge, function(err) { > > > > >> ... //(2) > > > > >> }); > > > > >> ... //(3) > > > > >> > > > > >> の場合の処理順は (1)->(3)->(2) となります。 > > > > >> > > > > >> 問題のコードは HTTP を処理するリスナの中に > > > > >> あるので、 > > > > >> > > > > >> http.createServer(function(req, res) { > > > > >> ... //(1) > > > > >> foo(hoge, function(err) { > > > > >> ... //(2) > > > > >> }); > > > > >> ... //(3) > > > > >> }); > > > > >> > > > > >> となるわけですが、ここで二つの HTTP リクエスト、 > > > > >> A と B がほとんど同時に (ただし A が先に) 到着した > > > > >> 場合の処理順を考えてみましょう。 > > > > >> > > > > >> その場合は、 > > > > >> A(1)->A(3)->A(2)->B(1)->B(3)->B(2) となる。。。 > > > > >> 保証はなくて、 > > > > >> > > > > >> A(1)->A(3)->B(1)->B(3)->A(2)->B(2) だったり > > > > >> A(1)->A(3)->B(1)->B(3)->B(2)->A(2) だったり > > > > >> する可能性もあります。 > > > > >> リクエスト A に関する処理が全て完了してから > > > > >> リクエスト B の処理が開始されるとは限らない > > > > >> わけです。 > > > > >> > > > > >> さて、問題のコードからキャッシュの処理に着目すると、 > > > > >> > > > > >> http.createServer(function (request, response) { > > > > >> //(1) > > > > >> if(cache[f]) { > > > > >> response.writeHead(200, headers); > > > > >> response.end(cache[f].content); > > > > >> return; > > > > >> } > > > > >> > > > > >> fs.stat(f, function(err, stats) { > > > > >> //(2) > > > > >> var bufferOffset = 0; > > > > >> cache[f] = {content: new Buffer(stats.size)}; > > > > >> s.on('data', function(data) { > > > > >> //(3) > > > > >> data.copy(cache[f].content, bufferOffset); > > > > >> bufferOffset += data.length; > > > > >> }); > > > > >> }); > > > > >> }).listen(8080); > > > > >> > > > > >> という構造を抽出できるのですが、最初に到着した > > > > >> リクエスト A の処理では、 > > > > >> > > > > >> (1) キャッシュが存在しないことを確認し、 > > > > >> (2) キャッシュを作成して fs.stat() を呼び出し、 > > > > >> (3) 'data' イベントが発生するとキャッシュの中身を設定 > > > > >> > > > > >> と流れていくことはもう理解できているかと思います。 > > > > >> > > > > >> では、この途中のどこかで別のリクエスト B が到着すると > > > > >> どうなるでしょうか? > > > > >> > > > > >> A(1)->A(2)->A(3) > > > > >> > > > > >> と進む途中のどこかで、リクエスト B の処理が > > > > >> 挟まってきた場合を考えてみましょう。 > > > > >> > > > > >> A(1)->B(1)->? > > > > >> > > > > >> とか、 > > > > >> > > > > >> A(1)->A(2)->B(1)->? > > > > >> > > > > >> とか、タイミングによっては B の処理が進む経路も > > > > >> 変わってくるはずですね。 > > > > >> > > > > >> 掲載されたコードはどんな場合でも正しく > > > > >> 動作するでしょうか? > > > > >> うまくいかないのはどんな場合? > > > > >> どんな場合でもうまく動くようにするに > > > > >> どうしたらいいでしょうか? > > > > >> > > > > >> > > > > >> On Mon, 15 Apr 2013 06:18:31 -0700 (PDT), aporo4000 > > <hiros...@gmail.com <mailto:hiros...@gmail.com>> > > > > >> wrote: > > > > >> > > > > >> > いまちょうどそれを勉強してる初心者なので、みなさんみたいに > > スラスラ書いたり出来るように頑張ります! > > > > >> > > > > > >> > -- > > > > >> > > > > > >> > --- > > > > >> > このメールは Google グループのグループ「Node.js 日本ユーザ > > グループ」の登録者に送られています。 > > > > >> > このグループから退会し、メールの受信を停止するには、 > > nodejs_jp+unsubscr...@googlegroups.com > > <mailto:nodejs_jp%2bunsubscr...@googlegroups.com>にメールを送信します。 > > > > >> > その他のオプションについては、https://groups.google.com > > /groups/opt_out にアクセスしてください。 > > > > >> > > > > > >> > > > > >> > > > > >> -- > > > > >> { > > > > >> name: "Koichi Kobayashi", > > > > >> mail: "koi...@improvement.jp <mailto:koi...@improvement.jp>", > > > > >> blog: "http://d.hatena.ne.jp/koichik/", > > > > >> twitter: "@koichik" > > > > >> } > > > > >> > > > > >> -- > > > > > > > > > > --- > > > > > このメールは Google グループのグループ「Node.js 日本ユーザグ > > ループ」の登録者に送られています。 > > > > > このグループから退会し、メールの受信を停止するには、 > > nodejs_jp+unsubscr...@googlegroups.com > > <mailto:nodejs_jp%2bunsubscr...@googlegroups.com>にメールを送信します。 > > > > > その他のオプションについては、https://groups.google.com > > /groups/opt_out にアクセスしてください。 > > > > > > > > > > > > > > > > > > > > > > > -- > > > > > > > > --- > > > > このメールは Google グループのグループ「Node.js 日本ユーザグ > > ループ」の登録者に送られています。 > > > > このグループから退会し、メールの受信を停止するには、 > > nodejs_jp+unsubscr...@googlegroups.com > > <mailto:nodejs_jp%2bunsubscr...@googlegroups.com> にメールを送信します。 > > > > その他のオプションについては、https://groups.google.com/groups > > /opt_out にアクセスしてください。 > > > > > > > > > > > > > > > > > -- > > > { > > > name: "Koichi Kobayashi", > > > mail: "koic...@improvement.jp <mailto:koic...@improvement.jp>", > > > blog: "http://d.hatena.ne.jp/koichik/", > > > twitter: "@koichik" > > > } > > > > > > -- > > > > > > --- > > > このメールは Google グループのグループ「Node.js 日本ユーザグルー > > プ」の登録者に送られています。 > > > このグループから退会し、メールの受信を停止するには、 > > nodejs_jp+unsubscr...@googlegroups.com > > <mailto:nodejs_jp%2bunsubscr...@googlegroups.com> にメールを送信します。 > > > その他のオプションについては、https://groups.google.com/groups > > /opt_out にアクセスしてください。 > > > > > > > > > -- > > { > > name: "Koichi Kobayashi", > > mail: "koic...@improvement.jp <mailto:koic...@improvement.jp>", > > blog: "http://d.hatena.ne.jp/koichik/", > > twitter: "@koichik" > > } > > > > -- > > > > --- > > このメールは Google グループのグループ「Node.js 日本ユーザグループ」 > > の登録者に送られています。 > > このグループから退会し、メールの受信を停止するには、 > > nodejs_jp+unsubscr...@googlegroups.com > > <mailto:nodejs_jp%2bunsubscr...@googlegroups.com> にメールを送信します。 > > その他のオプションについては、https://groups.google.com/groups > > /opt_out にアクセスしてください。 > > > > > > > > -- > > > > --- > > このメールは Google グループのグループ「Node.js 日本ユーザグループ」の登 > > 録者に送られています。 > > このグループから退会し、メールの受信を停止するには、 > > nodejs_jp+unsubscr...@googlegroups.com にメールを送信します。 > > その他のオプションについては、https://groups.google.com/groups/opt_out > > にアクセスしてください。 > > > > > > -- > > --- > このメールは Google グループのグループ「Node.js 日本ユーザグループ」の登録者に送られています。 > このグループから退会し、メールの受信を停止するには、nodejs_jp+unsubscr...@googlegroups.com にメールを送信します。 > その他のオプションについては、https://groups.google.com/groups/opt_out にアクセスしてください。 > -- { name: "Koichi Kobayashi", mail: "koic...@improvement.jp", blog: "http://d.hatena.ne.jp/koichik/", twitter: "@koichik" } -- --- このメールは Google グループのグループ「Node.js 日本ユーザグループ」の登録者に送られています。 このグループから退会し、メールの受信を停止するには、nodejs_jp+unsubscr...@googlegroups.com にメールを送信します。 その他のオプションについては、https://groups.google.com/groups/opt_out にアクセスしてください。