Socket
Socket
Sign inDemoInstall

http2

Package Overview
Dependencies
0
Maintainers
1
Versions
44
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 0.0.4 to 0.0.5

doc/http.html

28

HISTORY.md
Version history
===============
### 0.0.1 (2013-06-23) ###
### 0.0.5 (2013-07-14) ###
* Frame serialization and deserialization largely done
* [Blog post](http://gabor.molnar.es/blog/2013/06/23/gsoc-week-number-1/)
* `Stream` class is done
* Public API stubs are in place
* [Blog post](http://gabor.molnar.es/blog/2013/07/08/gsoc-week-number-4/)
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.0.5.tar.gz)
### 0.0.4 (2013-07-08) ###
* Added logging
* Started `Stream` class implementation
* [Blog post](http://gabor.molnar.es/blog/2013/07/08/gsoc-week-number-3/)
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.0.4.tar.gz)
### 0.0.3 (2013-07-03) ###
* Header compression is ready
* [Blog post](http://gabor.molnar.es/blog/2013/07/03/the-http-slash-2-header-compression-implementation-of-node-http2/)
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.0.3.tar.gz)
### 0.0.2 (2013-07-01) ###

@@ -14,1 +29,8 @@

* [Blog post](http://gabor.molnar.es/blog/2013/07/01/gsoc-week-number-2/)
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.0.2.tar.gz)
### 0.0.1 (2013-06-23) ###
* Frame serialization and deserialization largely done
* [Blog post](http://gabor.molnar.es/blog/2013/06/23/gsoc-week-number-1/)
* [Tarball](https://github.com/molnarg/node-http2/archive/node-http2-0.0.1.tar.gz)

@@ -1,3 +0,7 @@

exports.framer = require('./lib/framer');
exports.compressor = require('./lib/compressor');
exports.connection = require('./lib/connection');
exports.framer = require('./lib/framer');
exports.http = require('./lib/http');
exports.https = require('./lib/https');
exports.stream = require('./lib/stream');
exports.utils = require('./lib/utils');

14

lib/compressor.js

@@ -638,7 +638,13 @@ // HTTP/2 compression is implemented by two [Transform Stream][1] subclasses that operate in

// * for each `chunk`, it pushes out a `chunk_frame` that is identical to the original, except
// the `data` property which holds the given chunk and the END_HEADERS/END_PUSH_STREAM flag
// that marks the last frame
// the `data` property which holds the given chunk, the END_HEADERS/END_PUSH_STREAM flag that
// marks the last frame and the END_STREAM flag which is always false before the end
for (var i = 0; i < chunks.length; i++) {
var flags = utils.clone(frame.flags);
flags['END_' + frame.type] = (i === chunks.length - 1);
var flags = utils.shallow_copy(frame.flags);
if (i === chunks.length - 1) {
flags['END_' + frame.type] = true;
} else {
flags['END_' + frame.type] = true;
flags['END_STREAM'] = false;
}
this.push({

@@ -645,0 +651,0 @@ type: frame.type,

@@ -9,3 +9,4 @@ var Duplex = require('stream').Duplex;

function Connection(socket, role) {
// `initialRequest` and `initialResponse` are optional
function Connection(role, socket, settings, initialRequest, initialResponse) {
Duplex.call(this, { objectMode: true });

@@ -15,3 +16,3 @@

this.role = role; // 'client' or 'server'
this.next_stream_id = (this.role === 'client') ? 1 : 2;
this.next_stream_id = (this.role === 'CLIENT') ? 1 : 2;
this.serializer = new Serializer();

@@ -18,0 +19,0 @@ this.deserializer = new Deserializer();

@@ -5,2 +5,4 @@ // The framer consists of two [Transform Stream][1] subclasses that operate in [object mode][2]:

// [2]: http://nodejs.org/api/stream.html#stream_new_stream_readable_options
var utils = require('./utils');
var Transform = require('stream').Transform;

@@ -23,3 +25,3 @@

function Serializer(log) {
this._log = log || require('./utils').nolog;
this._log = log || utils.nolog;
Transform.call(this, { objectMode: true });

@@ -61,3 +63,3 @@ }

function Deserializer(log) {
this._log = log || require('./utils').nolog;
this._log = log || utils.nolog;
Transform.call(this, { objectMode: true });

@@ -64,0 +66,0 @@ this._next(COMMON_HEADER_SIZE);

@@ -1,29 +0,35 @@

var EventEmitter = require('events').EventEmitter;
var Duplex = require('stream').Duplex;
var utils = require('../lib/utils');
var MAX_HTTP_PAYLOAD_SIZE = 16383; // TODO: this is repeated in multiple files
// Stream is a [Duplex stream](http://nodejs.org/api/stream.html#stream_class_stream_duplex)
// subclass that implements the [HTTP/2 Stream](http://http2.github.io/http2-spec/#rfc.section.3.4)
// concept.
var Duplex = require('stream').Duplex;
exports.Stream = Stream;
var MAX_HTTP_PAYLOAD_SIZE = 16383; // TODO: this is repeated in multiple files
// The main aspects of managing the stream are:
function Stream(log) {
var self = this;
Duplex.call(this);
this.upstream = new Duplex();
this._read = this.upstream._read = function noop() {};
this.upstream._write = function(frame, encoding, done) {
self.emit('receiving', frame);
done();
};
// * every method uses the common logger object
this._log = log || require('./utils').nolog;
// * receiving and sending stream management commands
this._initializeManagement();
// * sending and receiving frames to/from the upstream connection
this._initializeUpstream();
// * maintaining the state of the stream (idle, open, closed, etc.) and error detection
this._initializeState();
// * flow control, which includes forwarding data from/to the user on the Duplex stream interface
// (`write()`, `end()`, `pipe()`)
this._initializeFlowControl();
}
Stream.prototype = Object.create(EventEmitter.prototype, { constructor: { value: Stream } });
Stream.prototype = Object.create(Duplex.prototype, { constructor: { value: Stream } });

@@ -33,7 +39,20 @@ // Managing the stream

Stream.prototype._send = function _send(frame) {
this.upstream.push(frame);
this.emit('sending', frame);
// PUSH_PROMISE and HEADERS are forwarded to the user through events. When error happens, we first
// close the stream.
Stream.prototype._initializeManagement = function _initializeManagement() {
this.on('receiving', function(frame) {
if (frame.type === 'PUSH_PROMISE') {
this.emit('promise', frame.headers);
} else if (frame.type === 'HEADERS') {
this.emit('headers', frame.headers);
}
});
this.on('error', function() {
this.push(null);
});
};
// For sending management frames, the `this._send(frame)` method is used. It notifies the state
// management code about the sent frames (using the 'sending' event) so we don't have to manage
// state transitions here.
Stream.prototype.promise = function promise(headers) {

@@ -54,9 +73,2 @@ this._send({

Stream.prototype.close = function close() {
this._send({
type: 'RST_STREAM'
});
};
Stream.prototype.reset = function reset(error) {

@@ -69,2 +81,66 @@ this._send({

// Managing the upstream connection
// --------------------------------
// The incoming and the generated outgoing frames are received/transmitted on the `this.upsteam`
// Duplex stream which operates in [object mode][1]. The [Connection](connection.html) object
// instantiating the stream will read and write frames to/from it.
// [1]: http://nodejs.org/api/stream.html#stream_new_stream_readable_options
Stream.prototype._initializeUpstream = function _initializeUpstream() {
this._flush_timer = undefined;
this.on('finish', this._finishing.bind(this));
this.upstream = new Duplex({ objectMode: true });
this.upstream._queue = [];
this.upstream._read = function noop() {};
// When there's an incoming frame, we let the world know this by emitting a 'receiving' event.
var log = this._log;
this.upstream._write = function(frame, encoding, done) {
log.debug({ frame: frame }, 'Receiving frame');
this.emit('receiving', frame);
done();
};
};
// Frames can be sent upstream using the `_send` method. The frames to be sent are put into the
// `upstream._queue` first, and are flushed immediately on the beginning of the next turn.
Stream.prototype._send = function _send(frame) {
frame.flags = frame.flags || {};
this.upstream._queue.push(frame);
if (!this._flush_timer) {
this._flush_timer = setImmediate(this._flush.bind(this));
}
};
Stream.prototype._flush = function _flush() {
var frame;
while(frame = this.upstream._queue.shift()) {
this.upstream.emit('sending', frame);
this._log.debug({ frame: frame }, 'Sending frame');
this.upstream.push(frame);
}
this._flush_timer = undefined;
};
// The reason for using an output queue is this. When the stream is finishing (the user calls
// `end()` on it), then we have to set the `END_STREAM` flag on the last object.
//
// If there's no frame in the queue, then we create a 0 length DATA frame. We could do this
// all the time, but putting the flag on an existing frame is a nice optimization.
var empty_buffer = new Buffer(0);
Stream.prototype._finishing = function _finishing() {
var length = this.upstream._queue.length;
if (length === 0) {
this._send({
type: 'DATA',
flags: { END_STREAM: true },
data: empty_buffer
});
} else {
var last_frame = this.upstream._queue[length - 1];
last_frame.flags.END_STREAM = true;
}
};
// [Stream States](http://tools.ietf.org/id/draft-unicorn-httpbis-http2-01.html#StreamStates)

@@ -99,8 +175,11 @@ // ----------------

// Streams begin in the IDLE state and transitions happen when there's an incoming or outgoing frame
Stream.prototype._initializeState = function _initializeState() {
this.state = 'IDLE';
this.on('sending', this._transition.bind(this, true));
this.on('receiving', this._transition.bind(this, false));
this.upstream.on('sending', this._transition.bind(this, true));
this.upstream.on('receiving', this._transition.bind(this, false));
};
// Only `_setState` should change `this.state` directly. It also logs the state change and notifies
// interested parties using the 'state' event.
Stream.prototype._setState = function transition(state) {

@@ -115,9 +194,10 @@ if (this.state !== state) {

// `_transition` is called every time there's an incoming or outgoing frame. It manages state
// transitions, and detects stream errors.
// transitions, and detects stream errors. A stream error is always caused by a frame that is not
// allowed in the current state.
Stream.prototype._transition = function transition(sending, frame) {
var receiving = !sending;
var error = false;
var error = undefined;
switch (this.state) {
// All streams start in the "idle" state. In this state, no frames have been exchanged.
// All streams start in the **idle** state. In this state, no frames have been exchanged.
//

@@ -129,13 +209,18 @@ // * Sending or receiving a HEADERS frame causes the stream to become "open".

// The state of the stream becomes "reserved (remote)".
//
// When the HEADERS frame contains the END_STREAM flags, then two state transitions happen.
case 'IDLE':
if (frame.type === 'HEADERS') {
this._setState('OPEN');
if (frame.flags.END_STREAM) {
this._setState(sending ? 'HALF_CLOSED_LOCAL' : 'HALF_CLOSED_REMOTE');
}
} else if (frame.type === 'PUSH_PROMISE') {
this._setState(sending ? 'RESERVED_LOCAL' : 'RESERVED_REMOTE');
} else { // TODO: Not well defined. https://github.com/http2/http2-spec/issues/165
error = true;
error = 'PROTOCOL_ERROR';
}
break;
// A stream in the "reserved (local)" state is one that has been promised by sending a
// A stream in the **reserved (local)** state is one that has been promised by sending a
// PUSH_PROMISE frame.

@@ -151,10 +236,10 @@ //

this._setState('HALF_CLOSED_REMOTE');
} else if ( sending && frame.type === 'RST_STREAM') {
} else if (sending && frame.type === 'RST_STREAM') {
this._setState('CLOSED');
} else { // TODO: Not well defined. https://github.com/http2/http2-spec/issues/165
error = true;
error = 'PROTOCOL_ERROR';
}
break;
// A stream in the "reserved (remote)" state has been reserved by a remote peer.
// A stream in the **reserved (remote)** state has been reserved by a remote peer.
//

@@ -171,7 +256,7 @@ // * Either endpoint can send a RST_STREAM frame to cause the stream to become "closed". This

} else {
error = true;
error = 'PROTOCOL_ERROR';
}
break;
// The "open" state is where both peers can send frames. In this state, sending peers observe
// The **open** state is where both peers can send frames. In this state, sending peers observe
// advertised stream level flow control limits.

@@ -186,12 +271,10 @@ //

case 'OPEN':
if (frame.flags && frame.flags.END_STREAM) {
if (frame.flags.END_STREAM) {
this._setState(sending ? 'HALF_CLOSED_LOCAL' : 'HALF_CLOSED_REMOTE');
} else if (frame.type === 'RST_STREAM') {
this._setState('CLOSED');
} else if (frame.type !== 'DATA') { // TODO: Not well defined. https://github.com/http2/http2-spec/issues/165
error = true;
}
} // Anything else is OK
break;
// A stream that is "half closed (local)" cannot be used for sending frames.
// A stream that is **half closed (local)** cannot be used for sending frames.
//

@@ -201,10 +284,10 @@ // * A stream transitions from this state to "closed" when a frame that contains a END_STREAM

case 'HALF_CLOSED_LOCAL':
if (frame.type === 'RST_STREAM' || (receiving && frame.flags && frame.flags.END_STREAM)) {
if (frame.type === 'RST_STREAM' || (receiving && frame.flags.END_STREAM)) {
this._setState('CLOSED');
} else if (sending) { // TODO: what is valid here?
error = true;
}
} else if (sending) {
error = 'PROTOCOL_ERROR';
} // Receiving anything is OK
break;
// A stream that is "half closed (remote)" is no longer being used by the peer to send frames.
// A stream that is **half closed (remote)** is no longer being used by the peer to send frames.
// In this state, an endpoint is no longer obligated to maintain a receiver flow control window

@@ -218,10 +301,10 @@ // if it performs flow control.

case 'HALF_CLOSED_REMOTE':
if (frame.type === 'RST_STREAM' || (sending && frame.flags && frame.flags.END_STREAM)) {
if (frame.type === 'RST_STREAM' || (sending && frame.flags.END_STREAM)) {
this._setState('CLOSED');
} else if (receiving) { // // TODO: what is valid here?
error = true;
}
} else if (receiving) {
error = 'PROTOCOL_ERROR';
} // Sending anything is OK
break;
// The "closed" state is the terminal state.
// The **closed** state is the terminal state.
//

@@ -241,15 +324,27 @@ // * An endpoint MUST NOT send frames on a closed stream. An endpoint that receives a frame

case 'CLOSED':
error = true;
if (receiving && frame.type === 'PUSH_PROMISE') {
this._setState('RESERVED_REMOTE');
} else if (!(sending && frame.type === 'RST_STREAM')) {
error = 'PROTOCOL_ERROR';
} // TODO: act based on the reason for termination.
break;
}
// TODO: DATA frame handling. Sending is allowed in HALF_CLOSED_REMOTE and OPEN?
// Common error handling.
if (error) {
var info = { error: error, frame: frame, state: this.state };
// * When sending something invalid, throwing an exception, since it is probably a bug.
if (sending) {
this._log.error({ frame: frame, state: this.state }, 'Stream error: sending illegal frame.');
this._log.error(info, 'Stream error: sending illegal frame.');
throw new Error('Sending illegal frame (' + frame.type + ') in ' + this.state + ' state.');
} else {
this._log.error({ frame: frame, state: this.state }, 'Stream error: received illegal frame.');
this.emit('error', 'PROTOCOL_ERROR');
}
// * When receiving something invalid, sending an RST_STREAM using the `reset` method.
// This will automatically cause a transition to the CLOSED state.
else {
this._log.error(info, 'Stream error: received illegal frame.');
this.state = 'CLOSED';
this.reset(error);
}
}

@@ -264,10 +359,7 @@ };

// sender is permitted to transmit. Two flow control windows are applicable; the stream flow control
// window and the connection flow control window.
// window and the connection flow control window. The stream only manages the flow control `window`.
Stream.prototype._initializeFlowControl = function _initializeFlowControl() {
this._read = function noop() {};
this.window = INITIAL_WINDOW_SIZE;
this.on('receiving', function(frame) {
if (frame.type === 'WINDOW_UPDATE') {
this._updateWindow(frame);
}
});
this.upstream.on('receiving', this._updateWindow.bind(this));
};

@@ -280,4 +372,5 @@

// A SETTINGS frame can alter the initial flow control window size for all current streams. When the
// value of SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST adjust the size of all stream flow
// control windows that it maintains by the difference between the new value and the old value.
// value of SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST adjust the size of all stream by
// calling the `setInitialWindowSize` method. The window size has to be modified by the difference
// between the new value and the old value.
Stream.prototype.setInitialWindowSize = function setInitialWindowSize(initialWindowSize) {

@@ -288,3 +381,4 @@ this.window = this.window - this._initialWindowSize + initialWindowSize;

// Flow control can be disabled for all streams on the connection using the
// Flow control can be disabled for all streams on the connection using the `disableFlowControl`
// method. This may happen when there's a SETTINGS frame received with the
// SETTINGS_FLOW_CONTROL_OPTIONS setting.

@@ -295,28 +389,32 @@ Stream.prototype.disableFlowControl = function disableFlowControl() {

// A sender that receives a WINDOW_UPDATE frame updates the corresponding window by the amount
// specified in the frame.
// The `_updateWindow` method gets called every time there's an incoming frame. It filters out
// WINDOW_UPDATE frames, and then modifies the modifies the flow control window:
//
// Flow control can be disabled for an individual stream by sending a WINDOW_UPDATE with the
// END_FLOW_CONTROL flag set. The payload of a WINDOW_UPDATE frame that has the END_FLOW_CONTROL
// flag set is ignored.
Stream.prototype._updateWindow = function _received(frame) {
if (frame.flags.END_FLOW_CONTROL) {
this.disableFlowControl();
} else {
this.window += frame.window_size;
// * Flow control can be disabled for an individual stream by sending a WINDOW_UPDATE with the
// END_FLOW_CONTROL flag set. The payload of a WINDOW_UPDATE frame that has the END_FLOW_CONTROL
// flag set is ignored.
// * A sender that receives a WINDOW_UPDATE frame updates the corresponding window by the amount
// specified in the frame.
Stream.prototype._updateWindow = function _updateWindow(frame) {
if (frame.type === 'WINDOW_UPDATE') {
if (frame.flags.END_FLOW_CONTROL) {
this.disableFlowControl();
} else {
this.window += frame.window_size;
}
this.emit('window_update');
}
this.emit('window_update');
};
// After sending a flow controlled frame, the sender reduces the space available in both windows by
// the length of the transmitted frame. For flow control calculations, the 8 byte frame header is
// not counted.
// When the user wants to write a buffer into the stream
Stream.prototype._write = function _write(buffer, encoding, done) {
// * The incoming buffer is cut into pieces that are not larger than `MAX_HTTP_PAYLOAD_SIZE`
var chunks = utils.cut(buffer, MAX_HTTP_PAYLOAD_SIZE);
var sent = 0;
// * Chunks are wrapped in DATA frames and sent out until all of them are sent or the flow control
// `window` is not enough to send a chunk
while (chunks.length > 0 && chunks[0].length <= this.window) {
var chunk = chunks.shift();
sent += chunk.length;
this.window -= chunk.length;
this._send({

@@ -327,9 +425,19 @@ type: 'DATA',

});
// * After sending a flow controlled frame, the sender reduces the space available the window by
// the length of the transmitted frame. For flow control calculations, the 8 byte frame header
// is not counted.
this.window -= chunk.length;
}
// * If all of the chunks are sent, we are done
if (chunks.length === 0) {
done();
} else {
}
// * Otherwise the process has to continue when a window_update occurs. It is guaranteed by
// the Duplex stream class, that there will be no more calls to `_write` until we are done
else {
this.once('window_update', this._write.bind(this, buffer.slice(sent), encoding, done));
}
};

@@ -28,4 +28,4 @@ // Concatenate an array of buffers into a new buffer

// Clone an object
exports.clone = function clone(object) {
// Shallow copy inspired by underscore's [clone](http://underscorejs.org/#clone)
exports.shallow_copy = function shallow_copy(object) {
var clone = {};

@@ -32,0 +32,0 @@ for (var key in object) {

{
"name": "http2",
"version": "0.0.4",
"version": "0.0.5",
"description": "An HTTP/2 server implementation",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -9,3 +9,3 @@ node-http2

I post weekly status updates [on my blog](http://gabor.molnar.es/blog/categories/google-summer-of-code/). Short version: framing layer and compression is ready.
I post weekly status updates [on my blog](http://gabor.molnar.es/blog/categories/google-summer-of-code/). Short version: framing layer, compression and stream implementation is ready. Connection handling is next.

@@ -24,3 +24,3 @@ Installation

The developer documentation is generated using [docco](http://jashkenas.github.io/docco/), and is located in the `doc` directory. API documentation is coming later.
The developer documentation is generated using [docco](http://jashkenas.github.io/docco/), and is located in the `doc` directory. API documentation is coming later. The docs are usually updated only before releasing a new version. To regenerate them manually, run `npm run-script prepublish`.

@@ -40,2 +40,1 @@ Running the tests

Copyright (C) 2013 Gábor Molnár <gabor@molnar.es>

@@ -121,3 +121,3 @@ var expect = require('chai').expect;

describe('static method .integer(I, N)', function() {
it('should return an array of buffers that represent the encoded form of the string str', function() {
it('should return an array of buffers that represent the N-prefix coded form of the integer I', function() {
for (var i = 0; i < test_strings.length; i++) {

@@ -129,4 +129,4 @@ var test = test_strings[i];

});
describe('static method .string(stringbuffer)', function() {
it('should return an array of buffers that represent the encoded form of the string buffer', function() {
describe('static method .string(string)', function() {
it('should return an array of buffers that represent the encoded form of the string', function() {
for (var i = 0; i < test_strings.length; i++) {

@@ -133,0 +133,0 @@ var test = test_strings[i];

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc