Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | var parser = exports; |
michael@0 | 2 | |
michael@0 | 3 | var spdy = require('../spdy'), |
michael@0 | 4 | util = require('util'), |
michael@0 | 5 | stream = require('stream'), |
michael@0 | 6 | Buffer = require('buffer').Buffer; |
michael@0 | 7 | |
michael@0 | 8 | var legacy = !stream.Duplex; |
michael@0 | 9 | |
michael@0 | 10 | if (legacy) { |
michael@0 | 11 | var DuplexStream = stream; |
michael@0 | 12 | } else { |
michael@0 | 13 | var DuplexStream = stream.Duplex; |
michael@0 | 14 | } |
michael@0 | 15 | |
michael@0 | 16 | // |
michael@0 | 17 | // ### function Parser (connection) |
michael@0 | 18 | // #### @connection {spdy.Connection} connection |
michael@0 | 19 | // SPDY protocol frames parser's @constructor |
michael@0 | 20 | // |
michael@0 | 21 | function Parser(connection) { |
michael@0 | 22 | DuplexStream.call(this); |
michael@0 | 23 | |
michael@0 | 24 | this.drained = true; |
michael@0 | 25 | this.paused = false; |
michael@0 | 26 | this.buffer = []; |
michael@0 | 27 | this.buffered = 0; |
michael@0 | 28 | this.waiting = 8; |
michael@0 | 29 | |
michael@0 | 30 | this.state = { type: 'frame-head' }; |
michael@0 | 31 | this.socket = connection.socket; |
michael@0 | 32 | this.connection = connection; |
michael@0 | 33 | this.framer = null; |
michael@0 | 34 | |
michael@0 | 35 | this.connection = connection; |
michael@0 | 36 | |
michael@0 | 37 | if (legacy) { |
michael@0 | 38 | this.readable = this.writable = true; |
michael@0 | 39 | } |
michael@0 | 40 | } |
michael@0 | 41 | util.inherits(Parser, DuplexStream); |
michael@0 | 42 | |
michael@0 | 43 | // |
michael@0 | 44 | // ### function create (connection) |
michael@0 | 45 | // #### @connection {spdy.Connection} connection |
michael@0 | 46 | // @constructor wrapper |
michael@0 | 47 | // |
michael@0 | 48 | parser.create = function create(connection) { |
michael@0 | 49 | return new Parser(connection); |
michael@0 | 50 | }; |
michael@0 | 51 | |
michael@0 | 52 | // |
michael@0 | 53 | // ### function destroy () |
michael@0 | 54 | // Just a stub. |
michael@0 | 55 | // |
michael@0 | 56 | Parser.prototype.destroy = function destroy() { |
michael@0 | 57 | }; |
michael@0 | 58 | |
michael@0 | 59 | // |
michael@0 | 60 | // ### function _write (data, encoding, cb) |
michael@0 | 61 | // #### @data {Buffer} chunk of data |
michael@0 | 62 | // #### @encoding {Null} encoding |
michael@0 | 63 | // #### @cb {Function} callback |
michael@0 | 64 | // Writes or buffers data to parser |
michael@0 | 65 | // |
michael@0 | 66 | Parser.prototype._write = function write(data, encoding, cb) { |
michael@0 | 67 | // Legacy compatibility |
michael@0 | 68 | if (!cb) cb = function() {}; |
michael@0 | 69 | |
michael@0 | 70 | if (data !== undefined) { |
michael@0 | 71 | // Buffer data |
michael@0 | 72 | this.buffer.push(data); |
michael@0 | 73 | this.buffered += data.length; |
michael@0 | 74 | } |
michael@0 | 75 | |
michael@0 | 76 | // Notify caller about state (for piping) |
michael@0 | 77 | if (this.paused) return false; |
michael@0 | 78 | |
michael@0 | 79 | // We shall not do anything until we get all expected data |
michael@0 | 80 | if (this.buffered < this.waiting) return cb(); |
michael@0 | 81 | |
michael@0 | 82 | // Mark parser as not drained |
michael@0 | 83 | if (data !== undefined) this.drained = false; |
michael@0 | 84 | |
michael@0 | 85 | var self = this, |
michael@0 | 86 | buffer = new Buffer(this.waiting), |
michael@0 | 87 | sliced = 0, |
michael@0 | 88 | offset = 0; |
michael@0 | 89 | |
michael@0 | 90 | while (this.waiting > offset && sliced < this.buffer.length) { |
michael@0 | 91 | var chunk = this.buffer[sliced++], |
michael@0 | 92 | overmatched = false; |
michael@0 | 93 | |
michael@0 | 94 | // Copy chunk into `buffer` |
michael@0 | 95 | if (chunk.length > this.waiting - offset) { |
michael@0 | 96 | chunk.copy(buffer, offset, 0, this.waiting - offset); |
michael@0 | 97 | |
michael@0 | 98 | this.buffer[--sliced] = chunk.slice(this.waiting - offset); |
michael@0 | 99 | this.buffered += this.buffer[sliced].length; |
michael@0 | 100 | |
michael@0 | 101 | overmatched = true; |
michael@0 | 102 | } else { |
michael@0 | 103 | chunk.copy(buffer, offset); |
michael@0 | 104 | } |
michael@0 | 105 | |
michael@0 | 106 | // Move offset and decrease amount of buffered data |
michael@0 | 107 | offset += chunk.length; |
michael@0 | 108 | this.buffered -= chunk.length; |
michael@0 | 109 | |
michael@0 | 110 | if (overmatched) break; |
michael@0 | 111 | } |
michael@0 | 112 | |
michael@0 | 113 | // Remove used buffers |
michael@0 | 114 | this.buffer = this.buffer.slice(sliced); |
michael@0 | 115 | |
michael@0 | 116 | // Executed parser for buffered data |
michael@0 | 117 | this.paused = true; |
michael@0 | 118 | this.execute(this.state, buffer, function (err, waiting) { |
michael@0 | 119 | // And unpause once execution finished |
michael@0 | 120 | self.paused = false; |
michael@0 | 121 | |
michael@0 | 122 | // Propagate errors |
michael@0 | 123 | if (err) { |
michael@0 | 124 | cb(); |
michael@0 | 125 | return self.emit('error', err); |
michael@0 | 126 | } |
michael@0 | 127 | |
michael@0 | 128 | // Set new `waiting` |
michael@0 | 129 | self.waiting = waiting; |
michael@0 | 130 | |
michael@0 | 131 | if (self.waiting <= self.buffered) { |
michael@0 | 132 | self._write(undefined, null, cb); |
michael@0 | 133 | } else { |
michael@0 | 134 | process.nextTick(function() { |
michael@0 | 135 | if (self.drained) return; |
michael@0 | 136 | |
michael@0 | 137 | // Mark parser as drained |
michael@0 | 138 | self.drained = true; |
michael@0 | 139 | self.emit('drain'); |
michael@0 | 140 | }); |
michael@0 | 141 | |
michael@0 | 142 | cb(); |
michael@0 | 143 | } |
michael@0 | 144 | }); |
michael@0 | 145 | }; |
michael@0 | 146 | |
michael@0 | 147 | if (legacy) { |
michael@0 | 148 | // |
michael@0 | 149 | // ### function write (data, encoding, cb) |
michael@0 | 150 | // #### @data {Buffer} chunk of data |
michael@0 | 151 | // #### @encoding {Null} encoding |
michael@0 | 152 | // #### @cb {Function} callback |
michael@0 | 153 | // Legacy method |
michael@0 | 154 | // |
michael@0 | 155 | Parser.prototype.write = Parser.prototype._write; |
michael@0 | 156 | |
michael@0 | 157 | // |
michael@0 | 158 | // ### function end () |
michael@0 | 159 | // Stream's end() implementation |
michael@0 | 160 | // |
michael@0 | 161 | Parser.prototype.end = function end() { |
michael@0 | 162 | this.emit('end'); |
michael@0 | 163 | }; |
michael@0 | 164 | } |
michael@0 | 165 | |
michael@0 | 166 | // |
michael@0 | 167 | // ### function createFramer (version) |
michael@0 | 168 | // #### @version {Number} Protocol version, either 2 or 3 |
michael@0 | 169 | // Sets framer instance on Parser's instance |
michael@0 | 170 | // |
michael@0 | 171 | Parser.prototype.createFramer = function createFramer(version) { |
michael@0 | 172 | if (spdy.protocol[version]) { |
michael@0 | 173 | this.emit('version', version); |
michael@0 | 174 | |
michael@0 | 175 | this.framer = new spdy.protocol[version].Framer( |
michael@0 | 176 | spdy.utils.zwrap(this.connection._deflate), |
michael@0 | 177 | spdy.utils.zwrap(this.connection._inflate) |
michael@0 | 178 | ); |
michael@0 | 179 | |
michael@0 | 180 | // Propagate framer to connection |
michael@0 | 181 | this.connection._framer = this.framer; |
michael@0 | 182 | this.emit('framer', this.framer); |
michael@0 | 183 | } else { |
michael@0 | 184 | this.emit( |
michael@0 | 185 | 'error', |
michael@0 | 186 | new Error('Unknown protocol version requested: ' + version) |
michael@0 | 187 | ); |
michael@0 | 188 | } |
michael@0 | 189 | }; |
michael@0 | 190 | |
michael@0 | 191 | // |
michael@0 | 192 | // ### function execute (state, data, callback) |
michael@0 | 193 | // #### @state {Object} Parser's state |
michael@0 | 194 | // #### @data {Buffer} Incoming data |
michael@0 | 195 | // #### @callback {Function} continuation callback |
michael@0 | 196 | // Parse buffered data |
michael@0 | 197 | // |
michael@0 | 198 | Parser.prototype.execute = function execute(state, data, callback) { |
michael@0 | 199 | if (state.type === 'frame-head') { |
michael@0 | 200 | var header = state.header = spdy.protocol.generic.parseHeader(data); |
michael@0 | 201 | |
michael@0 | 202 | // Lazily create framer |
michael@0 | 203 | if (!this.framer && header.control) { |
michael@0 | 204 | this.createFramer(header.version); |
michael@0 | 205 | } |
michael@0 | 206 | |
michael@0 | 207 | state.type = 'frame-body'; |
michael@0 | 208 | callback(null, header.length); |
michael@0 | 209 | } else if (state.type === 'frame-body') { |
michael@0 | 210 | var self = this; |
michael@0 | 211 | |
michael@0 | 212 | // Data frame |
michael@0 | 213 | if (!state.header.control) { |
michael@0 | 214 | return onFrame(null, { |
michael@0 | 215 | type: 'DATA', |
michael@0 | 216 | id: state.header.id, |
michael@0 | 217 | fin: (state.header.flags & 0x01) === 0x01, |
michael@0 | 218 | compressed: (state.header.flags & 0x02) === 0x02, |
michael@0 | 219 | data: data |
michael@0 | 220 | }); |
michael@0 | 221 | } else { |
michael@0 | 222 | // Control frame |
michael@0 | 223 | this.framer.execute(state.header, data, onFrame); |
michael@0 | 224 | } |
michael@0 | 225 | |
michael@0 | 226 | function onFrame(err, frame) { |
michael@0 | 227 | if (err) return callback(err); |
michael@0 | 228 | |
michael@0 | 229 | self.emit('frame', frame); |
michael@0 | 230 | |
michael@0 | 231 | state.type = 'frame-head'; |
michael@0 | 232 | callback(null, 8); |
michael@0 | 233 | }; |
michael@0 | 234 | } |
michael@0 | 235 | }; |