Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Stream2の基本

6,972 views

Published on

  • コメント気づかなくてすみません。(完全に見逃してた)
    ご指摘の通り、通常利用するなら自前でReadableを継承したクラスを作って利用していただくのが他に影響がなくて良いかと思います。単なるサンプルコードとして記載していたのでそこまで配慮してませんでした。ありがとうございます。
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
  • いつも大津さんの記事はとても参考になります!
    Readableの実装(22)、実際には直接Readableクラスの_readをいじるよりもutil.inherit()したものを書いたほうがいいわけですよね。
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here

Stream2の基本

  1. 1. Node.js v0.10 Stream2 のきほんStream2 ハッカソンIIJ 大津繁樹2013年5月18日
  2. 2. Stream は大事なクラスevents.EventEmitterstream.Streamnet.Sockettls.CleartextStreamhttp.ServerRequest http.ClientResponsehttp.ServerResponse http.ClientRequesthttp.IncomingMessage http.outgoingMessagenode-v0.8
  3. 3. Stream は大事なクラスevents.EventEmitterstream.Streamnode-v0.10stream.Readable stream.Writablehttp.outgoingMessagestream.Duplexstream.Transformnet.Socket tls.CleartextStreamその他crypto系クラスcryptosign/verifyhttp.IncomingMessage
  4. 4. Stream1 おさらいReadableStreamsrcデータの流れdataイベントからデータの読み込みpause() による読み込み停止resume() による読み込み再開
  5. 5. Stream1 おさらいWritableStreamdstデータの流れ.write() によるデータの書き込み drainイベントによる書き込み待ちの解消通知
  6. 6. Stream1 おさらいReadableStreamWritableStreampipedataイベントで受けたデータを write()dstsrcwrite() が falseなら pause()drain イベントが発生したらresume()
  7. 7. Stream1 の問題node-v0.8 のマニュアルなんだろ?
  8. 8. Stream1 の問題var server = http.createServer(function(req, res) {setTimeout(function() {var data = ;req.on(data, function(chunk) {data += chunk;});req.on(end, function() {console.log(data);});res.writeHead(200);res.end();}, 1000);}).listen(8080);POSTデータをちゃんと取れる?
  9. 9. Stream1 の問題リスナReadableStreamデータReadable Stream が data イベントを生成した時にリスナが存在しなければ、 データは失われることに注意してください
  10. 10. Stream1 の問題開け→閉め→開け→閉め開け→閉め→開け→閉め開け→閉め→開け→閉め開け→閉め→開け→閉め不安定な流れに弱い
  11. 11. Stream2とは?内部タンク付きストリームストリーム内部にデータバッファを持つことによって、• データの取りこぼしをなくす• 不安定な流れに影響されにくくする• ただし上限値(highWaterMark: default 16kB)が決まっている• オブジェクト一つをストリームバッファとして扱
  12. 12. ReadableStreamきちんと理解するのは大変よ
  13. 13. ReadableStream のプレイヤーソース 実装者 消費者データの源ストリーム本体。ソースからデータを読み込み内部バッファに蓄えるストリームからデータを受け取り利用する人push() read()_read()readable
  14. 14. Readable 返り値実装者用_read(n) Streamから呼ばれる。 n は hwm を指す。 N/Apush(chunk) 内部バッファの最後にchunkを加える。 (chunkはBufferか文字列、ただし objectMode じゃない時)chunk を null にすると endイベントが発生true hwm以下false hwm 以上消費者用setEncoding(enc)StringDecoderを指定する。内部バッファにはdecodeしたものが加えられる。undefinedread(n) 内部バッファから n バイトデータを読み込む。 n がundefined の場合は内部データ全部バッファunshift(chunk) 内部バッファの先頭にchunkを加える。chunk を null にすると endイベントが発生true hwm以下false hwm 以上パイプpipe(dest,pipOpts)dest(WritableStream)とつなげて書き込む。 {end: false} のオプションで src の end イベントに伴って自動的に dst.endしない。複数 dest をつなげることが可能destunpipe(dest) dest との pipe を外す。destが指定されてない場合は全てのpipe を外す。src旧モード用resume() 省略 (このメソッドがされると旧モード互換になる。)pause() 省略 (このメソッドがされると旧モード互換になる。)wrap(stream) stream1 のオブジェクトを stream2 に変換するラッパー selfReadableStream API methods
  15. 15. ReadableStream API eventsイベント名 説明 引数readable 内部バッファのデータが消費できる時に発生するイベント。(でも実は発生条件が結構複雑)N/Aend ソースから読み込むデータの終わりを通知された時に発生するイベント。実質的には Readable.pull(null)されたタイミングで発生するものと考えていいだろう。(例外有)データを消費(Readable.read) されていないと発生しないので注意。N/Aerror 省略 エラーオブジェクトdata 旧モードでデータを受けるイベント。このリスナが登録されていると旧モード互換になる。データバッファ
  16. 16. 覚えておこう大切なことReadable.read(0)• ソースからデータを読み込む最初のトリガー• 常に null を返す• 消費者が利用するもの• Readable._read() を実行する。(でもストリーム内部状態によっては実行されない時も…)• 一番最初の readable イベントのリスナ登録に合わせて裏で実行される。
  17. 17. 覚えておこう大切なことReadable.read(n) ただし n>0• ストリーム内部バッファのデータを消費者が読み込む(消費する)関数• 消費者が利用する• n バイトを取得。読み込みデータが空だと nullが返る。• n を指定しない場合はストリーム内部の全データを取得。通常はこれ。• n が highWaterMark以上 だとストリームの最大値が上がるので注意。• 通常 readable イベントに合わせて実行する。(他でも使えるけど)
  18. 18. 覚えておこう大切なことReadable._read(n)• ソースからのデータを読み込み操作を開始する関数。ストリーム内部から実行される。• 実装者が利用する関数• read(nn)がトリガーだが、実行されるにはいくつか条件がある。大概は満タンチェックの 以下の条件。(内部バッファ量 – nn) < highWaterMark• 通常この中で ソースから取得したデータをストリームに(同期/非同期で) push するように実装する。• _read()を実装してないとストリームがエラーを throwする。空の関数でもいいので書いておく。• 渡される n は highWaterMark。参考程度で使わない。• push()した後に先読み機能でめちゃめちゃ _read() が呼ばれるので気を付けよう。(後述)
  19. 19. 覚えておこう大切なことReadable.push(chunk)• ソースからストリームへデータ(chunk)を送り込む関数。実装者が利用する。• pushされた chunk はストリームの内部バッファの最後に加えられる。• (おおよそ)push に合わせて readable イベントが発火すると考えていいだろう。(例外あり)• ストリームが満タン(内部バッファの容量がhighWaterMark以上)の時は push は false を返す。(でも無理やり詰め込める)• 何も渡すデータが無い時、 _read() が呼ばれたら push(‘’)をしておきましょう。• データ送りの終りは push(null) を実行。これがないと endイベントが発生しないので必ずやろう。• push() をするとストリームが先読み機能で内部に読み込めるだけ read(0) を繰り返してきます。無駄な _read() に
  20. 20. 覚えておこう大切なことReadable.unshift(chunk)• ストリームの内部バッファの先頭にデータを追加する関数。• 消費者が読み込み過ぎたデータをもとに戻す時に利用する。• 例としては、ヘッダ+ボディの解析時にヘッダ分割後残ったバッファ(ボディの頭の部分)をストリームにまた戻すような時に使う。
  21. 21. 覚えておこう大切なことold-mode の切り替え以下のいずれかの条件が合致すれば Stream1 の互換モードとなります。• data イベントリスナの登録• pause()の実行• resume() の実行(注意) data イベントのデータ読み込みとreadable イベントでのデータ読み込みを両立させないこと。
  22. 22. 一番簡単なサンプル(同期)var source = *‘a’, ‘b’, ‘c’, null+;var Readable = require(‘stream’).Readable;Readable.prototype._read = function() {this.push(source.shift()); // 同期};var rstream = new Readable();var data = ‘’;rstream.on(‘readable’, function() ,var b = rstream.read();if (b) data += b;});rstream.on(‘end’, function() ,console.log(‘data =‘ + b);});消費者実装者ソース
  23. 23. 一番簡単なサンプル動作概略rstream.read(0)内部バッファソース‘hoge’rstream.push(chunk)rstream._read(hwm)rstream.on(‘readable’,);消費者実装者
  24. 24. 一番簡単なサンプル動作概略内部バッファソース‘hoge’rstream.push(chunk)readableイベントrstream.on(‘readable’,function() {var b = rstream.read()});まとめてmaybeReadMorewhile() {rstream.read(0)}rstream_read(hwm)消費者実装者先読み
  25. 25. 一番簡単なサンプル動作概略消費者Readable内部バッファ空ソース rstream.push(null) endイベントrstream.on(‘end,function() {console.log()});実装者
  26. 26. 注意var Readable = require(‘readable’).Readable;Readable.prototype._read = function() {this.push(‘hoge’);}var rstream = new Readable();var data = ‘’;rstream.on(‘readable’, function() ,var b = rstream.read();if (b) data += b;});rstream.on(‘end’, function() ,console.log(‘data=‘ + data);});EOFがない!無限同期読み込み
  27. 27. 課題1• 同期のサンプルコードを1秒毎にデータを返す非同期のサンプルコードにしてみましょう。
  28. 28. 課題2• 課題1のコードをストリームの内部バッファが満タンだった時(push の返り値がfalseの時)に処理を止めるように改良しましょう。• 満タンになった場合は、消費者が全部内部バッファを空にして、ソースからの読み出しを継続させましょう。
  29. 29. 課題3以下の条件でソースのデータが全部読み込めるようにしましょう条件 値テスト1n1 > hwm > n2 n1 = 3, hwm =2, n2 = 1テスト2n1 > n2 > hwm n1 = 3, hwm =1, n2 = 2テスト3hwm > n1 > n2 n1 = 2, hwm =3, n2 = 1テスト3hwm > n2 > n1 n1 = 1, hwm =3, n2 = 2ソース: [a, b, c, d, e, f, g, h, i, j]読み込み: 非同期(1秒毎)読み込み条件• Readable.read(n1),• new Readable {highWaterMark: hwm})• Readable.push(chunk) ただし chunk.length === n2
  30. 30. WritableStreamこっちはわりと簡単
  31. 31. WritableStream のプレイヤー実装者リソースストリームが書き込み中の場合書き込みリクエストを一時待機ストリームからデータを書き込まれるwrite() _write()callback空になったらdraincallback生産者
  32. 32. WritableStream API methodsWritableStream 返り値実装者用_write(chunk, encoding, cb) ストリームからリソースへデータを書き出す関数。実装者が利用です。完了したら cb を実行。書き込みにエラーがあったら 第一引数をエラーにして cb を実行する。生産者用write(chunk, [encoding], [cb]); WritableStream を経由してリソースにデータを書き込む。書き込みが完了したら cb が起動される。true hwm以下false hwm 以上end([chunk], [encoding], [cb]); 書き込み終了を実行する。リソースがすべて書き込まれたら finish イベントが発火する。
  33. 33. WritableStream API eventsイベント名 説明 引数drain WritableStream の内部バッファが空になったことを示すイベントN/Aerror エラーが発生したイベント エラーオブジェクトfinish end() 後全てのデータの書き込みが完了したことを示すイベントN/Apipe パイプがつながったことを示すイベント sourceunpipe パイプが外されたことを示すイベント source
  34. 34. 一番簡単なサンプル(同期)var dest = [];var Writable = require(‘stream’).Writable;Writable.prototype._write = function(chunk, encoding, cb) {dest.push(chunk); // 同期cb(null);};var wstream = new Writable();var chunk = new Buffer(“abc”);wstream.write(chunk, function(err) {if (err) throw err;console.log(“write finished”);});wstream.on (‘finish’, function() ,console.log(‘dest=‘ + dest);});wstream.end();生産者実装者書き込み先
  35. 35. 同期書き込みサンプルの動き単一書き込み→完了書き込み先var dest = [];wstream._write(chunk, encoding, cb)wstream.write(chunk,wcb)消費者実装者dest.push(chunk)cb(err) wcb(err)内部バッファ同期書き込み完了
  36. 36. 同期書き込みサンプルの動き複数書き込み書き込み先var dest = [];wstream._write(chunk, encoding, cb)wstream.write(chunk1,wcb1)消費者実装者内部バッファ書き込み中wstream.write(chunk2,wcb2)chunk1
  37. 37. 同期書き込みサンプルの動き複数書き込み書き込み先var dest = [];wstream._write(chunk, encoding, cb)wstream.write(chunk1,wcb1)消費者実装者内部バッファ書き込み完了wstream.write(chunk2,wcb2)内部バッファ中で待機中の書き込みリクエストを実行chunk2
  38. 38. 同期書き込みサンプルの動き複数書き込み書き込み先var dest = [];wstream._write(chunk, encoding, cb)wstream.write(chunk1,wcb1)消費者実装者内部バッファ空書き込み完了wstream.write(chunk2,wcb2)内部バッファ中で待機中の書き込みリクエストを実行wcb1(err)wcb2(err)drainイベント
  39. 39. 同期書き込みサンプルの動きfinish イベント書き込み先var dest =*“abc”+;wstream.end()消費者実装者内部バッファfinishイベント内部バッファが空
  40. 40. var dest = [], chunks = [a, b, c, d, e, f, g];var Writable = require(stream).Writable;Writable.prototype._write = function(chunk, encoding, cb) {setTimeout(function() {dest.push(chunk); cb(null);}, 1000);};var wstream = new Writable();var sWrite = func tion( c ) {wstream.write(c, function(err) {if (err) throw err;console.log("write(" + c + ") completed");});};for(var i = 0; i < chunks.length; i++) sWrite(chunks[i]);wstream.on(finish, function() { console.log(dest= + dest); });wstream.end();非同期書き込みサンプル
  41. 41. 課題4オレオレ パイプを作ろうReadablestreamhwm = 1WritableStreamhwm = 1src dstランダム(MAX10秒)で非同期読み出しランダム(MAX10秒)で非同期書き出し_readread/write_writevar src = *‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘g’+; var dst = [];
  42. 42. DuplexStream
  43. 43. Readable/Writable の多重継承events.EventEmitterstream.Streamstream.Readable stream.Writablestream.Duplexnet.Socket tls.CleartextStream
  44. 44. Readable用 Writable用DuplexStreamReadableとWritableが独立してるだけ(allowHalfOpenオプションが追加されてます。 defaultが true なのでどちらか(Readable/Writable)が end しても片方は終了せず、そのままです。)
  45. 45. 課題5エコーバックストリームDuplexStream書き込み読み込みあるデータを書き込んだら、すぐ読みだされるストリームオブジェクトを作ろう。
  46. 46. TransformStream
  47. 47. TransformStreamstream.Duplexstream.Transform書き込まれたデータを変換処理して読みだせるストリーム
  48. 48. TransformStream API methodsTransformStream 返り値実装者用_transform(chunk, encoding, cb) 変換処理を実装する関数。 write(chunk) されたデータが chunk として渡されるのでchunk を実装者で好きに処理してpush(chunk) する。終わったら cb() して chunk の処理が終わったことを伝える。_flush(cb) ストリームデータのデータ変換の最後に追加の処理をしたいときに実装する。終了したら cb() を実行。その直後に finish イベントが発火する。
  49. 49. 簡単なサンプル(一文字シフト)var Transform = require(stream).Transform;Transform.prototype._transform = function(chunk, encoding, cb) {for(var i = 0; i < chunk.length; i++) chunk[i]++;this.push(chunk);cb();};Transform.prototype._flush = function(cb) {this.push(new Buffer(|saigo|));cb();};var tstream = new Transform();var data = ;tstream.on(readable, function() {var b = tstream.read();if (b) data += b;});tstream.on(end, function() {console.log(transformed = + data);});tstream.end(new Buffer(abc));変換送り込み完了通知
  50. 50. TransformStream の動きtstream._transform(chunk,encoding, cb)tstream.write(chunk,wcb);tstream.end();消費者実装者this.push(変換後データ)cb(err)Writable内部バッファReadalbe内部バッファデータ変換処理tstream.on(‘readable,function() {var b = tstream.read();});
  51. 51. 課題6なにも変換をしない TransformStream を作ってみる。
  52. 52. 課題6書き込まれたデータの中で [a-zA-Z] を rot13する Transform stream を作成しなさい。
  53. 53. PassThroughStream
  54. 54. PassThroughStreamstream.PassThroughstream.Transformデータの変換をしないTransformStream課題6で作ったのと同じ
  55. 55. おわりQ&A

×