select-stream.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. var { Transform } = require('stream');
  2. var sysUtil = require('util');
  3. var util = require('./util');
  4. function SelectStream(options) {
  5. if (!(this instanceof SelectStream)) return new SelectStream(options);
  6. Transform.call(this, options);
  7. Object.assign(this, {
  8. totalLength: 0, // current message block's total length
  9. headerLength: 0, // current message block's header length
  10. payloadRestLength: 0, // current message block's rest payload length
  11. header: null, // current message block's header
  12. chunk: Buffer.alloc(0), // the data chunk being parsed
  13. callback: null, // current _transform function's callback
  14. });
  15. }
  16. SelectStream.prototype = {
  17. /**
  18. * process data chunk
  19. * concat the last chunk and current chunk
  20. * try to parse current message block's totalLength and headerLength
  21. * try to parse current message block's header
  22. * try to parse current message block's payload
  23. */
  24. processChunk(chunk, encoding, callback) {
  25. Object.assign(this, {
  26. chunk: Buffer.concat([this.chunk, chunk], this.chunk.length + chunk.length),
  27. encoding,
  28. callback,
  29. });
  30. this.parseLength();
  31. this.parseHeader();
  32. this.parsePayload();
  33. },
  34. /**
  35. * try to parse current message block's totalLength and headerLength
  36. */
  37. parseLength() {
  38. if (!this.callback) {
  39. return;
  40. }
  41. if (this.totalLength && this.headerLength) {
  42. return;
  43. }
  44. if (this.chunk.length >= 12) {
  45. this.totalLength = this.chunk.readInt32BE(0);
  46. this.headerLength = this.chunk.readInt32BE(4);
  47. this.payloadRestLength = this.totalLength - this.headerLength - 16;
  48. this.chunk = this.chunk.slice(12);
  49. } else {
  50. this.callback();
  51. this.callback = null;
  52. }
  53. },
  54. /**
  55. * try to parse current message block's header
  56. * if header[':message-type'] is error, callback the error, emit error to next stream
  57. */
  58. parseHeader() {
  59. if (!this.callback) {
  60. return;
  61. }
  62. if (!this.headerLength || this.header) {
  63. return;
  64. }
  65. if (this.chunk.length >= this.headerLength) {
  66. var header = {};
  67. var offset = 0;
  68. while (offset < this.headerLength) {
  69. var headerNameLength = this.chunk[offset] * 1;
  70. var headerName = this.chunk.toString('ascii', offset + 1, offset + 1 + headerNameLength);
  71. var headerValueLength = this.chunk.readInt16BE(offset + headerNameLength + 2);
  72. var headerValue = this.chunk.toString(
  73. 'ascii',
  74. offset + headerNameLength + 4,
  75. offset + headerNameLength + 4 + headerValueLength
  76. );
  77. header[headerName] = headerValue;
  78. offset += headerNameLength + 4 + headerValueLength;
  79. }
  80. this.header = header;
  81. this.chunk = this.chunk.slice(this.headerLength);
  82. this.checkErrorHeader();
  83. } else {
  84. this.callback();
  85. this.callback = null;
  86. }
  87. },
  88. /**
  89. * try to parse current message block's payload
  90. */
  91. parsePayload() {
  92. var self = this;
  93. if (!this.callback) {
  94. return;
  95. }
  96. if (this.chunk.length <= this.payloadRestLength) {
  97. this.payloadRestLength -= this.chunk.length;
  98. this.pushData(this.chunk);
  99. this.chunk = Buffer.alloc(0);
  100. } else if (this.chunk.length < this.payloadRestLength + 4) {
  101. this.pushData(this.chunk.slice(0, this.payloadRestLength));
  102. this.chunk = this.chunk.slice(this.payloadRestLength);
  103. this.payloadRestLength = 0;
  104. } else {
  105. this.pushData(this.chunk.slice(0, this.payloadRestLength));
  106. this.chunk = this.chunk.slice(this.payloadRestLength + 4);
  107. this.totalLength = 0;
  108. this.headerLength = 0;
  109. this.payloadRestLength = 0;
  110. this.header = null;
  111. }
  112. if (this.chunk.length && !(this.payloadRestLength === 0 && this.chunk.length < 4)) {
  113. process.nextTick(function () {
  114. self.processChunk(Buffer.alloc(0), self.encoding, self.callback);
  115. });
  116. } else {
  117. this.callback();
  118. this.callback = null;
  119. }
  120. },
  121. /**
  122. * if header[':event-type'] is Records, pipe payload to next stream
  123. */
  124. pushData(content) {
  125. if (this.header[':event-type'] === 'Records') {
  126. this.push(content);
  127. this.emit('message:records', content);
  128. } else if (this.header[':event-type'] === 'Progress') {
  129. var progress = util.xml2json(content.toString()).Progress;
  130. this.emit('message:progress', progress);
  131. } else if (this.header[':event-type'] === 'Stats') {
  132. var stats = util.xml2json(content.toString()).Stats;
  133. this.emit('message:stats', stats);
  134. } else if (this.header[':event-type'] === 'error') {
  135. var errCode = this.header[':error-code'];
  136. var errMessage = this.header[':error-message'];
  137. var err = new Error(errMessage);
  138. err.message = errMessage;
  139. err.name = err.code = errCode;
  140. this.emit('message:error', err);
  141. } else {
  142. // 'Continuation', 'End'
  143. this.emit('message:' + this.header[':event-type'].toLowerCase());
  144. }
  145. },
  146. /**
  147. * if header[':message-type'] is error, callback the error, emit error to next stream
  148. */
  149. checkErrorHeader() {
  150. if (this.header[':message-type'] === 'error') {
  151. this.callback(this.header);
  152. this.callback = null;
  153. }
  154. },
  155. /**
  156. * Transform Stream's implementations
  157. */
  158. _transform(chunk, encoding, callback) {
  159. this.processChunk(chunk, encoding, callback);
  160. },
  161. _flush(callback) {
  162. this.processChunk(Buffer.alloc(0), this.encoding, callback);
  163. },
  164. };
  165. sysUtil.inherits(SelectStream, Transform);
  166. module.exports = SelectStream;