testing/xpcshell/node-http2/node_modules/http2-protocol/lib/stream.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/testing/xpcshell/node-http2/node_modules/http2-protocol/lib/stream.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,615 @@
     1.4 +var assert = require('assert');
     1.5 +
     1.6 +// The Stream class
     1.7 +// ================
     1.8 +
     1.9 +// Stream is a [Duplex stream](http://nodejs.org/api/stream.html#stream_class_stream_duplex)
    1.10 +// subclass that implements the [HTTP/2 Stream](http://http2.github.io/http2-spec/#rfc.section.3.4)
    1.11 +// concept. It has two 'sides': one that is used by the user to send/receive data (the `stream`
    1.12 +// object itself) and one that is used by a Connection to read/write frames to/from the other peer
    1.13 +// (`stream.upstream`).
    1.14 +
    1.15 +var Duplex = require('stream').Duplex;
    1.16 +
    1.17 +exports.Stream = Stream;
    1.18 +
    1.19 +// Public API
    1.20 +// ----------
    1.21 +
    1.22 +// * **new Stream(log)**: create a new Stream
    1.23 +//
    1.24 +// * **Event: 'headers' (headers)**: signals incoming headers
    1.25 +//
    1.26 +// * **Event: 'promise' (stream, headers)**: signals an incoming push promise
    1.27 +//
    1.28 +// * **Event: 'priority' (priority)**: signals a priority change. `priority` is a number between 0
    1.29 +//     (highest priority) and 2^31-1 (lowest priority). Default value is 2^30.
    1.30 +//
    1.31 +// * **Event: 'error' (type)**: signals an error
    1.32 +//
    1.33 +// * **headers(headers)**: send headers
    1.34 +//
    1.35 +// * **promise(headers): Stream**: promise a stream
    1.36 +//
    1.37 +// * **priority(priority)**: set the priority of the stream. Priority can be changed by the peer
    1.38 +//   too, but once it is set locally, it can not be changed remotely.
    1.39 +//
    1.40 +// * **reset(error)**: reset the stream with an error code
    1.41 +//
    1.42 +// * **upstream**: a [Flow](flow.js) that is used by the parent connection to write/read frames
    1.43 +//   that are to be sent/arrived to/from the peer and are related to this stream.
    1.44 +//
    1.45 +// Headers are always in the [regular node.js header format][1].
    1.46 +// [1]: http://nodejs.org/api/http.html#http_message_headers
    1.47 +
    1.48 +// Constructor
    1.49 +// -----------
    1.50 +
    1.51 +// The main aspects of managing the stream are:
    1.52 +function Stream(log) {
    1.53 +  Duplex.call(this);
    1.54 +
    1.55 +  // * logging
    1.56 +  this._log = log.child({ component: 'stream', s: this });
    1.57 +
    1.58 +  // * receiving and sending stream management commands
    1.59 +  this._initializeManagement();
    1.60 +
    1.61 +  // * sending and receiving frames to/from the upstream connection
    1.62 +  this._initializeDataFlow();
    1.63 +
    1.64 +  // * maintaining the state of the stream (idle, open, closed, etc.) and error detection
    1.65 +  this._initializeState();
    1.66 +}
    1.67 +
    1.68 +Stream.prototype = Object.create(Duplex.prototype, { constructor: { value: Stream } });
    1.69 +
    1.70 +// Managing the stream
    1.71 +// -------------------
    1.72 +
    1.73 +// the default stream priority is 2^30
    1.74 +var DEFAULT_PRIORITY = Math.pow(2, 30);
    1.75 +var MAX_PRIORITY = Math.pow(2, 31) - 1;
    1.76 +
    1.77 +// PUSH_PROMISE and HEADERS are forwarded to the user through events.
    1.78 +Stream.prototype._initializeManagement = function _initializeManagement() {
    1.79 +  this._resetSent = false;
    1.80 +  this._priority = DEFAULT_PRIORITY;
    1.81 +  this._letPeerPrioritize = true;
    1.82 +};
    1.83 +
    1.84 +Stream.prototype.promise = function promise(headers) {
    1.85 +  var stream = new Stream(this._log);
    1.86 +  stream._priority = Math.min(this._priority + 1, MAX_PRIORITY);
    1.87 +  this._pushUpstream({
    1.88 +    type: 'PUSH_PROMISE',
    1.89 +    flags: {},
    1.90 +    stream: this.id,
    1.91 +    promised_stream: stream,
    1.92 +    headers: headers
    1.93 +  });
    1.94 +  return stream;
    1.95 +};
    1.96 +
    1.97 +Stream.prototype._onPromise = function _onPromise(frame) {
    1.98 +  this.emit('promise', frame.promised_stream, frame.headers);
    1.99 +};
   1.100 +
   1.101 +Stream.prototype.headers = function headers(headers) {
   1.102 +  this._pushUpstream({
   1.103 +    type: 'HEADERS',
   1.104 +    flags: {},
   1.105 +    stream: this.id,
   1.106 +    headers: headers
   1.107 +  });
   1.108 +};
   1.109 +
   1.110 +Stream.prototype._onHeaders = function _onHeaders(frame) {
   1.111 +  if (frame.priority !== undefined) {
   1.112 +    this.priority(frame.priority, true);
   1.113 +  }
   1.114 +  this.emit('headers', frame.headers);
   1.115 +};
   1.116 +
   1.117 +Stream.prototype.priority = function priority(priority, peer) {
   1.118 +  if ((peer && this._letPeerPrioritize) || !peer) {
   1.119 +    if (!peer) {
   1.120 +      this._letPeerPrioritize = false;
   1.121 +
   1.122 +      var lastFrame = this.upstream.getLastQueuedFrame();
   1.123 +      if (lastFrame && ((lastFrame.type === 'HEADERS') || (lastFrame.type === 'PRIORITY'))) {
   1.124 +        lastFrame.priority = priority;
   1.125 +      } else {
   1.126 +        this._pushUpstream({
   1.127 +          type: 'PRIORITY',
   1.128 +          flags: {},
   1.129 +          stream: this.id,
   1.130 +          priority: priority
   1.131 +        });
   1.132 +      }
   1.133 +    }
   1.134 +
   1.135 +    this._log.debug({ priority: priority }, 'Changing priority');
   1.136 +    this.emit('priority', priority);
   1.137 +    this._priority = priority;
   1.138 +  }
   1.139 +};
   1.140 +
   1.141 +Stream.prototype._onPriority = function _onPriority(frame) {
   1.142 +  this.priority(frame.priority, true);
   1.143 +};
   1.144 +
   1.145 +// Resetting the stream. Normally, an endpoint SHOULD NOT send more than one RST_STREAM frame for
   1.146 +// any stream.
   1.147 +Stream.prototype.reset = function reset(error) {
   1.148 +  if (!this._resetSent) {
   1.149 +    this._resetSent = true;
   1.150 +    this._pushUpstream({
   1.151 +      type: 'RST_STREAM',
   1.152 +      flags: {},
   1.153 +      stream: this.id,
   1.154 +      error: error
   1.155 +    });
   1.156 +  }
   1.157 +};
   1.158 +
   1.159 +// Data flow
   1.160 +// ---------
   1.161 +
   1.162 +// The incoming and the generated outgoing frames are received/transmitted on the `this.upstream`
   1.163 +// [Flow](flow.html). The [Connection](connection.html) object instantiating the stream will read
   1.164 +// and write frames to/from it. The stream itself is a regular [Duplex stream][1], and is used by
   1.165 +// the user to write or read the body of the request.
   1.166 +// [1]: http://nodejs.org/api/stream.html#stream_class_stream_duplex
   1.167 +
   1.168 +//     upstream side                  stream                  user side
   1.169 +//
   1.170 +//                    +------------------------------------+
   1.171 +//                    |                                    |
   1.172 +//                    +------------------+                 |
   1.173 +//                    |     upstream     |                 |
   1.174 +//                    |                  |                 |
   1.175 +//                    +--+               |              +--|
   1.176 +//            read()  |  |  _send()      |    _write()  |  |  write(buf)
   1.177 +//     <--------------|B |<--------------|--------------| B|<------------
   1.178 +//                    |  |               |              |  |
   1.179 +//            frames  +--+               |              +--|  buffers
   1.180 +//                    |  |               |              |  |
   1.181 +//     -------------->|B |---------------|------------->| B|------------>
   1.182 +//      write(frame)  |  |  _receive()   |     _read()  |  |  read()
   1.183 +//                    +--+               |              +--|
   1.184 +//                    |                  |                 |
   1.185 +//                    |                  |                 |
   1.186 +//                    +------------------+                 |
   1.187 +//                    |                                    |
   1.188 +//                    +------------------------------------+
   1.189 +//
   1.190 +//     B: input or output buffer
   1.191 +
   1.192 +var Flow = require('./flow').Flow;
   1.193 +
   1.194 +Stream.prototype._initializeDataFlow = function _initializeDataFlow() {
   1.195 +  this.id = undefined;
   1.196 +
   1.197 +  this._ended = false;
   1.198 +
   1.199 +  this.upstream = new Flow();
   1.200 +  this.upstream._log = this._log;
   1.201 +  this.upstream._send = this._send.bind(this);
   1.202 +  this.upstream._receive = this._receive.bind(this);
   1.203 +  this.upstream.write = this._writeUpstream.bind(this);
   1.204 +  this.upstream.on('error', this.emit.bind(this, 'error'));
   1.205 +
   1.206 +  this.on('finish', this._finishing);
   1.207 +};
   1.208 +
   1.209 +Stream.prototype._pushUpstream = function _pushUpstream(frame) {
   1.210 +  this.upstream.push(frame);
   1.211 +  this._transition(true, frame);
   1.212 +};
   1.213 +
   1.214 +// Overriding the upstream's `write` allows us to act immediately instead of waiting for the input
   1.215 +// queue to empty. This is important in case of control frames.
   1.216 +Stream.prototype._writeUpstream = function _writeUpstream(frame) {
   1.217 +  this._log.debug({ frame: frame }, 'Receiving frame');
   1.218 +
   1.219 +  var moreNeeded = Flow.prototype.write.call(this.upstream, frame);
   1.220 +
   1.221 +  // * Transition to a new state if that's the effect of receiving the frame
   1.222 +  this._transition(false, frame);
   1.223 +
   1.224 +  // * If it's a control frame. Call the appropriate handler method.
   1.225 +  if (frame.type === 'HEADERS') {
   1.226 +    this._onHeaders(frame);
   1.227 +  } else if (frame.type === 'PUSH_PROMISE') {
   1.228 +    this._onPromise(frame);
   1.229 +  } else if (frame.type === 'PRIORITY') {
   1.230 +    this._onPriority(frame);
   1.231 +  }
   1.232 +
   1.233 +  // * If it's an invalid stream level frame, emit error
   1.234 +  else if ((frame.type !== 'DATA') &&
   1.235 +           (frame.type !== 'WINDOW_UPDATE') &&
   1.236 +           (frame.type !== 'RST_STREAM')) {
   1.237 +    this._log.error({ frame: frame }, 'Invalid stream level frame');
   1.238 +    this.emit('error', 'PROTOCOL_ERROR');
   1.239 +  }
   1.240 +
   1.241 +  return moreNeeded;
   1.242 +};
   1.243 +
   1.244 +// The `_receive` method (= `upstream._receive`) gets called when there's an incoming frame.
   1.245 +Stream.prototype._receive = function _receive(frame, ready) {
   1.246 +  // * If it's a DATA frame, then push the payload into the output buffer on the other side.
   1.247 +  //   Call ready when the other side is ready to receive more.
   1.248 +  if (!this._ended && (frame.type === 'DATA')) {
   1.249 +    var moreNeeded = this.push(frame.data);
   1.250 +    if (!moreNeeded) {
   1.251 +      this._receiveMore = ready;
   1.252 +    }
   1.253 +  }
   1.254 +
   1.255 +  // * Any frame may signal the end of the stream with the END_STREAM flag
   1.256 +  if (!this._ended && (frame.flags.END_STREAM || (frame.type === 'RST_STREAM'))) {
   1.257 +    this.push(null);
   1.258 +    this._ended = true;
   1.259 +  }
   1.260 +
   1.261 +  // * Postpone calling `ready` if `push()` returned a falsy value
   1.262 +  if (this._receiveMore !== ready) {
   1.263 +    ready();
   1.264 +  }
   1.265 +};
   1.266 +
   1.267 +// The `_read` method is called when the user side is ready to receive more data. If there's a
   1.268 +// pending write on the upstream, then call its pending ready callback to receive more frames.
   1.269 +Stream.prototype._read = function _read() {
   1.270 +  if (this._receiveMore) {
   1.271 +    var receiveMore = this._receiveMore;
   1.272 +    delete this._receiveMore;
   1.273 +    receiveMore();
   1.274 +  }
   1.275 +};
   1.276 +
   1.277 +// The `write` method gets called when there's a write request from the user.
   1.278 +Stream.prototype._write = function _write(buffer, encoding, ready) {
   1.279 +  // * Chunking is done by the upstream Flow.
   1.280 +  var moreNeeded = this._pushUpstream({
   1.281 +    type: 'DATA',
   1.282 +    flags: {},
   1.283 +    stream: this.id,
   1.284 +    data: buffer
   1.285 +  });
   1.286 +
   1.287 +  // * Call ready when upstream is ready to receive more frames.
   1.288 +  if (moreNeeded) {
   1.289 +    ready();
   1.290 +  } else {
   1.291 +    this._sendMore = ready;
   1.292 +  }
   1.293 +};
   1.294 +
   1.295 +// The `_send` (= `upstream._send`) method is called when upstream is ready to receive more frames.
   1.296 +// If there's a pending write on the user side, then call its pending ready callback to receive more
   1.297 +// writes.
   1.298 +Stream.prototype._send = function _send() {
   1.299 +  if (this._sendMore) {
   1.300 +    var sendMore = this._sendMore;
   1.301 +    delete this._sendMore;
   1.302 +    sendMore();
   1.303 +  }
   1.304 +};
   1.305 +
   1.306 +// When the stream is finishing (the user calls `end()` on it), then we have to set the `END_STREAM`
   1.307 +// flag on the last frame. If there's no frame in the queue, or if it doesn't support this flag,
   1.308 +// then we create a 0 length DATA frame. We could do this all the time, but putting the flag on an
   1.309 +// existing frame is a nice optimization.
   1.310 +var emptyBuffer = new Buffer(0);
   1.311 +Stream.prototype._finishing = function _finishing() {
   1.312 +  var endFrame = {
   1.313 +    type: 'DATA',
   1.314 +    flags: { END_STREAM: true },
   1.315 +    stream: this.id,
   1.316 +    data: emptyBuffer
   1.317 +  };
   1.318 +  var lastFrame = this.upstream.getLastQueuedFrame();
   1.319 +  if (lastFrame && ((lastFrame.type === 'DATA') || (lastFrame.type === 'HEADERS'))) {
   1.320 +    this._log.debug({ frame: lastFrame }, 'Marking last frame with END_STREAM flag.');
   1.321 +    lastFrame.flags.END_STREAM = true;
   1.322 +    this._transition(true, endFrame);
   1.323 +  } else {
   1.324 +    this._pushUpstream(endFrame);
   1.325 +  }
   1.326 +};
   1.327 +
   1.328 +// [Stream States](http://tools.ietf.org/html/draft-ietf-httpbis-http2-10#section-5.1)
   1.329 +// ----------------
   1.330 +//
   1.331 +//                           +--------+
   1.332 +//                     PP    |        |    PP
   1.333 +//                  ,--------|  idle  |--------.
   1.334 +//                 /         |        |         \
   1.335 +//                v          +--------+          v
   1.336 +//         +----------+          |           +----------+
   1.337 +//         |          |          | H         |          |
   1.338 +//     ,---| reserved |          |           | reserved |---.
   1.339 +//     |   | (local)  |          v           | (remote) |   |
   1.340 +//     |   +----------+      +--------+      +----------+   |
   1.341 +//     |      |          ES  |        |  ES          |      |
   1.342 +//     |      | H    ,-------|  open  |-------.      | H    |
   1.343 +//     |      |     /        |        |        \     |      |
   1.344 +//     |      v    v         +--------+         v    v      |
   1.345 +//     |   +----------+          |           +----------+   |
   1.346 +//     |   |   half   |          |           |   half   |   |
   1.347 +//     |   |  closed  |          | R         |  closed  |   |
   1.348 +//     |   | (remote) |          |           | (local)  |   |
   1.349 +//     |   +----------+          |           +----------+   |
   1.350 +//     |        |                v                 |        |
   1.351 +//     |        |  ES / R    +--------+  ES / R    |        |
   1.352 +//     |        `----------->|        |<-----------'        |
   1.353 +//     |  R                  | closed |                  R  |
   1.354 +//     `-------------------->|        |<--------------------'
   1.355 +//                           +--------+
   1.356 +
   1.357 +// Streams begin in the IDLE state and transitions happen when there's an incoming or outgoing frame
   1.358 +Stream.prototype._initializeState = function _initializeState() {
   1.359 +  this.state = 'IDLE';
   1.360 +  this._initiated = undefined;
   1.361 +  this._closedByUs = undefined;
   1.362 +  this._closedWithRst = undefined;
   1.363 +};
   1.364 +
   1.365 +// Only `_setState` should change `this.state` directly. It also logs the state change and notifies
   1.366 +// interested parties using the 'state' event.
   1.367 +Stream.prototype._setState = function transition(state) {
   1.368 +  assert(this.state !== state);
   1.369 +  this._log.debug({ from: this.state, to: state }, 'State transition');
   1.370 +  this.state = state;
   1.371 +  this.emit('state', state);
   1.372 +};
   1.373 +
   1.374 +// A state is 'active' if the stream in that state counts towards the concurrency limit. Streams
   1.375 +// that are in the "open" state, or either of the "half closed" states count toward this limit.
   1.376 +function activeState(state) {
   1.377 +  return ((state === 'HALF_CLOSED_LOCAL') || (state === 'HALF_CLOSED_REMOTE') || (state === 'OPEN'));
   1.378 +}
   1.379 +
   1.380 +// `_transition` is called every time there's an incoming or outgoing frame. It manages state
   1.381 +// transitions, and detects stream errors. A stream error is always caused by a frame that is not
   1.382 +// allowed in the current state.
   1.383 +Stream.prototype._transition = function transition(sending, frame) {
   1.384 +  var receiving = !sending;
   1.385 +  var error = undefined;
   1.386 +
   1.387 +  var DATA = false, HEADERS = false, PRIORITY = false;
   1.388 +  var RST_STREAM = false, PUSH_PROMISE = false, WINDOW_UPDATE = false;
   1.389 +  switch(frame.type) {
   1.390 +    case 'DATA'         : DATA          = true; break;
   1.391 +    case 'HEADERS'      : HEADERS       = true; break;
   1.392 +    case 'PRIORITY'     : PRIORITY      = true; break;
   1.393 +    case 'RST_STREAM'   : RST_STREAM    = true; break;
   1.394 +    case 'PUSH_PROMISE' : PUSH_PROMISE  = true; break;
   1.395 +    case 'WINDOW_UPDATE': WINDOW_UPDATE = true; break;
   1.396 +  }
   1.397 +
   1.398 +  var previousState = this.state;
   1.399 +
   1.400 +  switch (this.state) {
   1.401 +    // All streams start in the **idle** state. In this state, no frames have been exchanged.
   1.402 +    //
   1.403 +    // * Sending or receiving a HEADERS frame causes the stream to become "open".
   1.404 +    //
   1.405 +    // When the HEADERS frame contains the END_STREAM flags, then two state transitions happen.
   1.406 +    case 'IDLE':
   1.407 +      if (HEADERS) {
   1.408 +        this._setState('OPEN');
   1.409 +        if (frame.flags.END_STREAM) {
   1.410 +          this._setState(sending ? 'HALF_CLOSED_LOCAL' : 'HALF_CLOSED_REMOTE');
   1.411 +        }
   1.412 +        this._initiated = sending;
   1.413 +      } else if (sending && RST_STREAM) {
   1.414 +        this._setState('CLOSED');
   1.415 +      } else {
   1.416 +        error = 'PROTOCOL_ERROR';
   1.417 +      }
   1.418 +      break;
   1.419 +
   1.420 +    // A stream in the **reserved (local)** state is one that has been promised by sending a
   1.421 +    // PUSH_PROMISE frame.
   1.422 +    //
   1.423 +    // * The endpoint can send a HEADERS frame. This causes the stream to open in a "half closed
   1.424 +    //   (remote)" state.
   1.425 +    // * Either endpoint can send a RST_STREAM frame to cause the stream to become "closed". This
   1.426 +    //   releases the stream reservation.
   1.427 +    // * An endpoint may receive PRIORITY frame in this state.
   1.428 +    // * An endpoint MUST NOT send any other type of frame in this state.
   1.429 +    case 'RESERVED_LOCAL':
   1.430 +      if (sending && HEADERS) {
   1.431 +        this._setState('HALF_CLOSED_REMOTE');
   1.432 +      } else if (RST_STREAM) {
   1.433 +        this._setState('CLOSED');
   1.434 +      } else if (receiving && PRIORITY) {
   1.435 +        /* No state change */
   1.436 +      } else {
   1.437 +        error = 'PROTOCOL_ERROR';
   1.438 +      }
   1.439 +      break;
   1.440 +
   1.441 +    // A stream in the **reserved (remote)** state has been reserved by a remote peer.
   1.442 +    //
   1.443 +    // * Either endpoint can send a RST_STREAM frame to cause the stream to become "closed". This
   1.444 +    //   releases the stream reservation.
   1.445 +    // * Receiving a HEADERS frame causes the stream to transition to "half closed (local)".
   1.446 +    // * An endpoint MAY send PRIORITY frames in this state to reprioritize the stream.
   1.447 +    // * Receiving any other type of frame MUST be treated as a stream error of type PROTOCOL_ERROR.
   1.448 +    case 'RESERVED_REMOTE':
   1.449 +      if (RST_STREAM) {
   1.450 +        this._setState('CLOSED');
   1.451 +      } else if (receiving && HEADERS) {
   1.452 +        this._setState('HALF_CLOSED_LOCAL');
   1.453 +      } else if (sending && PRIORITY) {
   1.454 +        /* No state change */
   1.455 +      } else {
   1.456 +        error = 'PROTOCOL_ERROR';
   1.457 +      }
   1.458 +      break;
   1.459 +
   1.460 +    // The **open** state is where both peers can send frames. In this state, sending peers observe
   1.461 +    // advertised stream level flow control limits.
   1.462 +    //
   1.463 +    // * From this state either endpoint can send a frame with a END_STREAM flag set, which causes
   1.464 +    //   the stream to transition into one of the "half closed" states: an endpoint sending a
   1.465 +    //   END_STREAM flag causes the stream state to become "half closed (local)"; an endpoint
   1.466 +    //   receiving a END_STREAM flag causes the stream state to become "half closed (remote)".
   1.467 +    // * Either endpoint can send a RST_STREAM frame from this state, causing it to transition
   1.468 +    //   immediately to "closed".
   1.469 +    case 'OPEN':
   1.470 +      if (frame.flags.END_STREAM) {
   1.471 +        this._setState(sending ? 'HALF_CLOSED_LOCAL' : 'HALF_CLOSED_REMOTE');
   1.472 +      } else if (RST_STREAM) {
   1.473 +        this._setState('CLOSED');
   1.474 +      } else {
   1.475 +        /* No state change */
   1.476 +      }
   1.477 +      break;
   1.478 +
   1.479 +    // A stream that is **half closed (local)** cannot be used for sending frames.
   1.480 +    //
   1.481 +    // * A stream transitions from this state to "closed" when a frame that contains a END_STREAM
   1.482 +    //   flag is received, or when either peer sends a RST_STREAM frame.
   1.483 +    // * An endpoint MAY send or receive PRIORITY frames in this state to reprioritize the stream.
   1.484 +    // * WINDOW_UPDATE can be sent by a peer that has sent a frame bearing the END_STREAM flag.
   1.485 +    case 'HALF_CLOSED_LOCAL':
   1.486 +      if (RST_STREAM || (receiving && frame.flags.END_STREAM)) {
   1.487 +        this._setState('CLOSED');
   1.488 +      } else if (receiving || (sending && (PRIORITY || WINDOW_UPDATE))) {
   1.489 +        /* No state change */
   1.490 +      } else {
   1.491 +        error = 'PROTOCOL_ERROR';
   1.492 +      }
   1.493 +      break;
   1.494 +
   1.495 +    // A stream that is **half closed (remote)** is no longer being used by the peer to send frames.
   1.496 +    // In this state, an endpoint is no longer obligated to maintain a receiver flow control window
   1.497 +    // if it performs flow control.
   1.498 +    //
   1.499 +    // * If an endpoint receives additional frames for a stream that is in this state it MUST
   1.500 +    //   respond with a stream error of type STREAM_CLOSED.
   1.501 +    // * A stream can transition from this state to "closed" by sending a frame that contains a
   1.502 +    //   END_STREAM flag, or when either peer sends a RST_STREAM frame.
   1.503 +    // * An endpoint MAY send or receive PRIORITY frames in this state to reprioritize the stream.
   1.504 +    // * A receiver MAY receive a WINDOW_UPDATE frame on a "half closed (remote)" stream.
   1.505 +    case 'HALF_CLOSED_REMOTE':
   1.506 +      if (RST_STREAM || (sending && frame.flags.END_STREAM)) {
   1.507 +        this._setState('CLOSED');
   1.508 +      } else if (sending || (receiving && (WINDOW_UPDATE || PRIORITY))) {
   1.509 +        /* No state change */
   1.510 +      } else {
   1.511 +        error = 'PROTOCOL_ERROR';
   1.512 +      }
   1.513 +      break;
   1.514 +
   1.515 +    // The **closed** state is the terminal state.
   1.516 +    //
   1.517 +    // * An endpoint MUST NOT send frames on a closed stream. An endpoint that receives a frame
   1.518 +    //   after receiving a RST_STREAM or a frame containing a END_STREAM flag on that stream MUST
   1.519 +    //   treat that as a stream error of type STREAM_CLOSED.
   1.520 +    // * WINDOW_UPDATE, PRIORITY or RST_STREAM frames can be received in this state for a short
   1.521 +    //   period after a frame containing an END_STREAM flag is sent.  Until the remote peer receives
   1.522 +    //   and processes the frame bearing the END_STREAM flag, it might send either frame type.
   1.523 +    //   Endpoints MUST ignore WINDOW_UPDATE frames received in this state, though endpoints MAY
   1.524 +    //   choose to treat WINDOW_UPDATE frames that arrive a significant time after sending
   1.525 +    //   END_STREAM as a connection error of type PROTOCOL_ERROR.
   1.526 +    // * If this state is reached as a result of sending a RST_STREAM frame, the peer that receives
   1.527 +    //   the RST_STREAM might have already sent - or enqueued for sending - frames on the stream
   1.528 +    //   that cannot be withdrawn. An endpoint that sends a RST_STREAM frame MUST ignore frames that
   1.529 +    //   it receives on closed streams after it has sent a RST_STREAM frame. An endpoint MAY choose
   1.530 +    //   to limit the period over which it ignores frames and treat frames that arrive after this
   1.531 +    //   time as being in error.
   1.532 +    // * An endpoint might receive a PUSH_PROMISE frame after it sends RST_STREAM. PUSH_PROMISE
   1.533 +    //   causes a stream to become "reserved". If promised streams are not desired, a RST_STREAM
   1.534 +    //   can be used to close any of those streams.
   1.535 +    case 'CLOSED':
   1.536 +      if ((sending && RST_STREAM) ||
   1.537 +          (receiving && this._closedByUs &&
   1.538 +           (this._closedWithRst || WINDOW_UPDATE || PRIORITY || RST_STREAM))) {
   1.539 +        /* No state change */
   1.540 +      } else {
   1.541 +        error = 'STREAM_CLOSED';
   1.542 +      }
   1.543 +      break;
   1.544 +  }
   1.545 +
   1.546 +  // Noting that the connection was closed by the other endpoint. It may be important in edge cases.
   1.547 +  // For example, when the peer tries to cancel a promised stream, but we already sent every data
   1.548 +  // on it, then the stream is in CLOSED state, yet we want to ignore the incoming RST_STREAM.
   1.549 +  if ((this.state === 'CLOSED') && (previousState !== 'CLOSED')) {
   1.550 +    this._closedByUs = sending;
   1.551 +    this._closedWithRst = RST_STREAM;
   1.552 +  }
   1.553 +
   1.554 +  // Sending/receiving a PUSH_PROMISE
   1.555 +  //
   1.556 +  // * Sending a PUSH_PROMISE frame marks the associated stream for later use. The stream state
   1.557 +  //   for the reserved stream transitions to "reserved (local)".
   1.558 +  // * Receiving a PUSH_PROMISE frame marks the associated stream as reserved by the remote peer.
   1.559 +  //   The state of the stream becomes "reserved (remote)".
   1.560 +  if (PUSH_PROMISE && !error) {
   1.561 +    /* This assertion must hold, because _transition is called immediately when a frame is written
   1.562 +       to the stream. If it would be called when a frame gets out of the input queue, the state
   1.563 +       of the reserved could have been changed by then. */
   1.564 +    assert(frame.promised_stream.state === 'IDLE', frame.promised_stream.state);
   1.565 +    frame.promised_stream._setState(sending ? 'RESERVED_LOCAL' : 'RESERVED_REMOTE');
   1.566 +    frame.promised_stream._initiated = sending;
   1.567 +  }
   1.568 +
   1.569 +  // Signaling how sending/receiving this frame changes the active stream count (-1, 0 or +1)
   1.570 +  if (this._initiated) {
   1.571 +    var change = (activeState(this.state) - activeState(previousState));
   1.572 +    if (sending) {
   1.573 +      frame.count_change = change;
   1.574 +    } else {
   1.575 +      frame.count_change(change);
   1.576 +    }
   1.577 +  } else if (sending) {
   1.578 +    frame.count_change = 0;
   1.579 +  }
   1.580 +
   1.581 +  // Common error handling.
   1.582 +  if (error) {
   1.583 +    var info = {
   1.584 +      error: error,
   1.585 +      frame: frame,
   1.586 +      state: this.state,
   1.587 +      closedByUs: this._closedByUs,
   1.588 +      closedWithRst: this._closedWithRst
   1.589 +    };
   1.590 +
   1.591 +    // * When sending something invalid, throwing an exception, since it is probably a bug.
   1.592 +    if (sending) {
   1.593 +      this._log.error(info, 'Sending illegal frame.');
   1.594 +      throw new Error('Sending illegal frame (' + frame.type + ') in ' + this.state + ' state.');
   1.595 +    }
   1.596 +
   1.597 +    // * When receiving something invalid, sending an RST_STREAM using the `reset` method.
   1.598 +    //   This will automatically cause a transition to the CLOSED state.
   1.599 +    else {
   1.600 +      this._log.error(info, 'Received illegal frame.');
   1.601 +      this.emit('error', error);
   1.602 +    }
   1.603 +  }
   1.604 +};
   1.605 +
   1.606 +// Bunyan serializers
   1.607 +// ------------------
   1.608 +
   1.609 +exports.serializers = {};
   1.610 +
   1.611 +var nextId = 0;
   1.612 +exports.serializers.s = function(stream) {
   1.613 +  if (!('_id' in stream)) {
   1.614 +    stream._id = nextId;
   1.615 +    nextId += 1;
   1.616 +  }
   1.617 +  return stream._id;
   1.618 +};

mercurial