h2cli
Version:
A command line interface for HTTP/2
218 lines (207 loc) • 7.87 kB
JavaScript
var OFFSET_LENGTH_FIELD = 0,
OFFSET_TYPE_FIELD = OFFSET_LENGTH_FIELD + 3,
OFFSET_FLAGS_FIELD = OFFSET_TYPE_FIELD + 1,
OFFSET_STREAMID_FIELD = OFFSET_FLAGS_FIELD + 1,
OFFSET_PAYLOAD = OFFSET_STREAMID_FIELD + 4;
var createFieldGetter = function (definition) {
switch (definition.type) {
case 'UINT':
if (definition.bitLength % 8 === 0) {
return function () {
return this.buf.readUIntBE(definition.offset, definition.bitLength >> 3);
};
} else {
return function () {
var value = this.buf.readUIntBE(definition.offset, (definition.bitLength >> 3) + 1);
return value & (0xFFFFFFFF >>> (32 - definition.bitLength));
};
}
break;
case 'BUFFER':
return function () {
if (definition.byteLength) {
return this.buf.slice(definition.offset, definition.offset + definition.byteLength);
} else {
return this.buf.slice(definition.offset);
}
};
default:
return function () {
return void(0);
};
}
};
var createFieldSetter = function (definition) {
switch (definition.type) {
case 'UINT':
if (definition.bitLength % 8 === 0) {
return function (value) {
this.buf.writeUIntBE(value, definition.offset, definition.bitLength >> 3);
};
} else {
return function (value) {
this.buf.writeUIntBE(value, definition.offset, (definition.bitLength >> 3) + 1);
};
}
break;
case 'BUFFER':
return function (value) {
if (definition.byteLength) {
value.copy(this.buf, definition.offset, 0, definition.byteLength);
} else {
this.buf = Buffer.concat([this.buf.slice(0, definition.offset), value]);
}
};
default:
return function (value) {
};
}
};
var createFlagGetter = function (definition) {
return function () {
return definition.value;
};
};
/******************
* BASE FRAME
******************/
var Http2Frame = module.exports.Http2Frame = function (buf) {
if (!(this instanceof Http2Frame)) {
return new Http2Frame(buf);
}
this.buf = buf;
};
Http2Frame.defineType = function(frame, definitions) {
Object.defineProperty(frame, 'TYPE_NAME', {
get: function () {
return definitions.NAME;
}
});
Object.defineProperty(frame, 'TYPE_CODE', {
get: function () {
return definitions.CODE;
}
});
};
Http2Frame.defineFields = function(frame, definitions) {
Object.keys(definitions).forEach(function (fieldName) {
var definition = definitions[fieldName];
Object.defineProperty(frame.prototype, fieldName, {
get: createFieldGetter(definition),
set: createFieldSetter(definition)
});
});
};
Http2Frame.defineFlags = function(frame, definitions) {
Object.keys(definitions).forEach(function (flagName) {
var definition = definitions[flagName];
Object.defineProperty(frame, 'FLAG_' + flagName, {
get: createFlagGetter(definition)
});
});
};
Object.defineProperty(Http2Frame, 'HEADER_SIZE', {
get: function () { return OFFSET_PAYLOAD; },
});
Http2Frame.defineFields(Http2Frame, {
length: { type: 'UINT', offset: OFFSET_LENGTH_FIELD, bitLength: 3 * 8 },
type: { type: 'UINT', offset: OFFSET_TYPE_FIELD, bitLength: 1 * 8 },
flags: { type: 'UINT', offset: OFFSET_FLAGS_FIELD, bitLength: 1 * 8 },
streamId: { type: 'UINT', offset: OFFSET_STREAMID_FIELD, bitLength: 31 },
});
Http2Frame.prototype.getBuffer = function () {
return this.buf;
};
Http2Frame.prototype.toString = function () {
var str = "[";
str += "Lenght: " + this.length + ", ";
str += "Type: " + this.constructor.TYPE_NAME + "(" + this.type + "), ";
str += "Flags: " + this.flags + ", ";
str += "StreamID: " + this.streamId;
str += "]";
return str;
};
var Http2DataFrame = module.exports.Http2DataFrame = require('./frame/data');
var Http2HeadersFrame = module.exports.Http2HeadersFrame = require('./frame/headers');
var Http2PriorityFrame = module.exports.Http2PriorityFrame = require('./frame/priority');
var Http2RstStreamFrame = module.exports.Http2RstStreamFrame = require('./frame/rst_stream');
var Http2SettingsFrame = module.exports.Http2SettingsFrame = require('./frame/settings');
var Http2PushPromiseFrame = module.exports.Http2PushPromiseFrame = require('./frame/push_promise');
var Http2PingFrame = module.exports.Http2PingFrame = require('./frame/ping');
var Http2GoawayFrame = module.exports.Http2GoawayFrame = require('./frame/goaway');
var Http2WindowUpdateFrame = module.exports.Http2WindowUpdateFrame = require('./frame/window_update');
var Http2ContinuationFrame = module.exports.Http2ContinuationFrame = require('./frame/continuation');
/******************
* Factory
******************/
var Http2FrameFactory = module.exports.Http2FrameFactory = {};
var klasses = [
Http2DataFrame,
Http2HeadersFrame,
Http2PriorityFrame,
Http2RstStreamFrame,
Http2SettingsFrame,
Http2PushPromiseFrame,
Http2PingFrame,
Http2GoawayFrame,
Http2WindowUpdateFrame,
Http2ContinuationFrame,
Http2Frame,
Http2Frame,
];
Http2FrameFactory.createFrame = function (buf) {
var klass = klasses[buf[OFFSET_TYPE_FIELD]];
if (klass) {
return new klass(buf);
} else {
return new Http2Frame(buf);
}
};
Http2FrameFactory.createRequestFrames = function (hpack, headers, usePadding) {
var maxFrameSize = 16384; // Should refer to SETTINGS_MAX_FRAME_SIZE
var frames = [], f, i;
var block = hpack.encode(headers.sort());
var maxPadLength = usePadding ? Http2HeadersFrame.MAX_PAD_LENGTH : 0;
var padLength = Math.floor(Math.random() * (maxPadLength + 1));
var blockLength = maxFrameSize - padLength - 6; // 6 = PadLength(1) + Stream Dependency(4) + Weight(1)
if (block.length > blockLength) {
f = new Http2HeadersFrame();
f.setBlock(block.slice(0, blockLength), padLength);
frames.push(f);
i = blockLength;
while (i < block.length) {
f = new Http2ContinuationFrame();
f.setBlock(block.slice(i, i + maxFrameSize));
i += maxFrameSize;
frames.push(f);
}
f.flags |= Http2HeadersFrame.FLAG_END_HEADERS;
} else {
f = new Http2HeadersFrame();
f.flags |= Http2HeadersFrame.FLAG_END_HEADERS;
f.setBlock(block, padLength);
frames.push(f);
}
return frames;
};
Http2FrameFactory.createDataFrames = function (data, usePadding) {
var maxFrameSize = 16384; // Should refer to SETTINGS_MAX_FRAME_SIZE
var frames = [], f, i = 0;
var maxPadLength = usePadding ? Http2DataFrame.MAX_PAD_LENGTH : 0;
var padLength, dataLength;
while (i < data.length) {
padLength = Math.floor(Math.random() * (maxPadLength + 1));
dataLength = maxFrameSize - padLength - 1; // 1 = PadLength(1)
f = new Http2DataFrame();
f.setData(data.slice(i, i + dataLength), padLength);
i += dataLength;
frames.push(f);
}
if (frames.length === 0) {
frames.push(new Http2DataFrame());
}
return frames;
};
Http2FrameFactory.registerFrame = function (type, klass) {
klasses[type] = klass;
};