大津です。

# 初心者スレなのに続けてしまってすみまへん

こいうい問題があって 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:
> 小林さん
> 
> ありがとうございます。
> 恥ずかしながら見えていませんでした。
> # 大津さんが仰っていたのもこれだったのですね。
> 
> 取り急ぎお礼まで。
> 
> 
> --
> [email protected] <mailto:[email protected]> (pc/mobile)
> mobile 090-1736-8716
> @yuichirowada on twitter
> 
> 
> 
> 2013/4/19 Koichi Kobayashi <[email protected] 
> <mailto:[email protected]>>
> 
>     小林 (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
>     <[email protected] <mailto:[email protected]>> 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
>     <[email protected] <mailto:[email protected]>> 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 <[email protected]
>     <mailto:[email protected]>>:
>      > >
>      > > > 全然違うかもしれませんが、最初に読み込むバイト数を指定すると
>     か!?ですか?
>      > > >
>      > > > 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
>     <[email protected] <mailto:[email protected]>>
>      > > >> wrote:
>      > > >>
>      > > >> > いまちょうどそれを勉強してる初心者なので、みなさんみたいに
>     スラスラ書いたり出来るように頑張ります!
>      > > >> >
>      > > >> > --
>      > > >> >
>      > > >> > ---
>      > > >> > このメールは Google グループのグループ「Node.js 日本ユーザ
>     グループ」の登録者に送られています。
>      > > >> > このグループから退会し、メールの受信を停止するには、
>     [email protected]
>     <mailto:nodejs_jp%[email protected]>にメールを送信します。
>      > > >> > その他のオプションについては、https://groups.google.com
>     /groups/opt_out にアクセスしてください。
>      > > >> >
>      > > >>
>      > > >>
>      > > >> --
>      > > >> {
>      > > >>   name: "Koichi Kobayashi",
>      > > >>   mail: "[email protected] <mailto:[email protected]>",
>      > > >>   blog: "http://d.hatena.ne.jp/koichik/";,
>      > > >>   twitter: "@koichik"
>      > > >> }
>      > > >>
>      > > >>  --
>      > > >
>      > > > ---
>      > > > このメールは Google グループのグループ「Node.js 日本ユーザグ
>     ループ」の登録者に送られています。
>      > > > このグループから退会し、メールの受信を停止するには、
>     [email protected]
>     <mailto:nodejs_jp%[email protected]>にメールを送信します。
>      > > > その他のオプションについては、https://groups.google.com
>     /groups/opt_out にアクセスしてください。
>      > > >
>      > > >
>      > > >
>      > >
>      > > --
>      > >
>      > > ---
>      > > このメールは Google グループのグループ「Node.js 日本ユーザグ
>     ループ」の登録者に送られています。
>      > > このグループから退会し、メールの受信を停止するには、
>     [email protected]
>     <mailto:nodejs_jp%[email protected]> にメールを送信します。
>      > > その他のオプションについては、https://groups.google.com/groups
>     /opt_out にアクセスしてください。
>      > >
>      > >
>      >
>      >
>      > --
>      > {
>      >   name: "Koichi Kobayashi",
>      >   mail: "[email protected] <mailto:[email protected]>",
>      >   blog: "http://d.hatena.ne.jp/koichik/";,
>      >   twitter: "@koichik"
>      > }
>      >
>      > --
>      >
>      > ---
>      > このメールは Google グループのグループ「Node.js 日本ユーザグルー
>     プ」の登録者に送られています。
>      > このグループから退会し、メールの受信を停止するには、
>     [email protected]
>     <mailto:nodejs_jp%[email protected]> にメールを送信します。
>      > その他のオプションについては、https://groups.google.com/groups
>     /opt_out にアクセスしてください。
>      >
> 
> 
>     --
>     {
>        name: "Koichi Kobayashi",
>        mail: "[email protected] <mailto:[email protected]>",
>        blog: "http://d.hatena.ne.jp/koichik/";,
>        twitter: "@koichik"
>     }
> 
>     --
> 
>     ---
>     このメールは Google グループのグループ「Node.js 日本ユーザグループ」
>     の登録者に送られています。
>     このグループから退会し、メールの受信を停止するには、
>     [email protected]
>     <mailto:nodejs_jp%[email protected]> にメールを送信します。
>     その他のオプションについては、https://groups.google.com/groups
>     /opt_out にアクセスしてください。
> 
> 
> 
> -- 
> 
> ---
> このメールは Google グループのグループ「Node.js 日本ユーザグループ」の登 
> 録者に送られています。
> このグループから退会し、メールの受信を停止するには、 
> [email protected] にメールを送信します。
> その他のオプションについては、https://groups.google.com/groups/opt_out 
> にアクセスしてください。
> 
> 

-- 

--- 
このメールは Google グループのグループ「Node.js 日本ユーザグループ」の登録者に送られています。
このグループから退会し、メールの受信を停止するには、[email protected] にメールを送信します。
その他のオプションについては、https://groups.google.com/groups/opt_out にアクセスしてください。


メールによる返信