Updated the files.

This commit is contained in:
Batuhan Berk Başoğlu 2024-02-08 19:38:41 -05:00
parent 1553e6b971
commit 753967d4f5
23418 changed files with 3784666 additions and 0 deletions

14
my-app/node_modules/spdy-transport/.travis.yml generated vendored Executable file
View file

@ -0,0 +1,14 @@
sudo: false
language: node_js
node_js:
- "6"
- "8"
- "10"
- "stable"
script:
- npm run lint
- npm test
- npm run coverage

76
my-app/node_modules/spdy-transport/README.md generated vendored Executable file
View file

@ -0,0 +1,76 @@
# spdy-transport
[![Build Status](https://travis-ci.org/spdy-http2/spdy-transport.svg?branch=master)](http://travis-ci.org/spdy-http2/spdy-transport)
[![NPM version](https://badge.fury.io/js/spdy-transport.svg)](http://badge.fury.io/js/spdy-transport)
[![dependencies Status](https://david-dm.org/spdy-http2/spdy-transport/status.svg?style=flat-square)](https://david-dm.org/spdy-http2/spdy-transport)
[![Standard - JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg?style=flat-square)](http://standardjs.com/)
[![Waffle](https://img.shields.io/badge/track-waffle-blue.svg?style=flat-square)](https://waffle.io/spdy-http2/node-spdy)
> SPDY/HTTP2 generic transport implementation.
## Usage
```javascript
var transport = require('spdy-transport');
// NOTE: socket is some stream or net.Socket instance, may be an argument
// of `net.createServer`'s connection handler.
var server = transport.connection.create(socket, {
protocol: 'http2',
isServer: true
});
server.on('stream', function(stream) {
console.log(stream.method, stream.path, stream.headers);
stream.respond(200, {
header: 'value'
});
stream.on('readable', function() {
var chunk = stream.read();
if (!chunk)
return;
console.log(chunk);
});
stream.on('end', function() {
console.log('end');
});
// And other node.js Stream APIs
// ...
});
```
## LICENSE
This software is licensed under the MIT License.
Copyright Fedor Indutny, 2015.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit
persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
[0]: http://json.org/
[1]: http://github.com/indutny/bud-backend
[2]: https://github.com/nodejs/io.js
[3]: https://github.com/libuv/libuv
[4]: http://openssl.org/

25
my-app/node_modules/spdy-transport/lib/spdy-transport.js generated vendored Executable file
View file

@ -0,0 +1,25 @@
'use strict'
var transport = exports
// Exports utils
transport.utils = require('./spdy-transport/utils')
// Export parser&framer
transport.protocol = {}
transport.protocol.base = require('./spdy-transport/protocol/base')
transport.protocol.spdy = require('./spdy-transport/protocol/spdy')
transport.protocol.http2 = require('./spdy-transport/protocol/http2')
// Window
transport.Window = require('./spdy-transport/window')
// Priority Tree
transport.Priority = require('./spdy-transport/priority')
// Export Connection and Stream
transport.Stream = require('./spdy-transport/stream').Stream
transport.Connection = require('./spdy-transport/connection').Connection
// Just for `transport.connection.create()`
transport.connection = transport.Connection

View file

@ -0,0 +1,845 @@
'use strict'
var util = require('util')
var transport = require('../spdy-transport')
var debug = {
server: require('debug')('spdy:connection:server'),
client: require('debug')('spdy:connection:client')
}
var EventEmitter = require('events').EventEmitter
var Stream = transport.Stream
function Connection (socket, options) {
EventEmitter.call(this)
var state = {}
this._spdyState = state
// NOTE: There's a big trick here. Connection is used as a `this` argument
// to the wrapped `connection` event listener.
// socket end doesn't necessarly mean connection drop
this.httpAllowHalfOpen = true
state.timeout = new transport.utils.Timeout(this)
// Protocol info
state.protocol = transport.protocol[options.protocol]
state.version = null
state.constants = state.protocol.constants
state.pair = null
state.isServer = options.isServer
// Root of priority tree (i.e. stream id = 0)
state.priorityRoot = new transport.Priority({
defaultWeight: state.constants.DEFAULT_WEIGHT,
maxCount: transport.protocol.base.constants.MAX_PRIORITY_STREAMS
})
// Defaults
state.maxStreams = options.maxStreams ||
state.constants.MAX_CONCURRENT_STREAMS
state.autoSpdy31 = options.protocol.name !== 'h2' && options.autoSpdy31
state.acceptPush = options.acceptPush === undefined
? !state.isServer
: options.acceptPush
if (options.maxChunk === false) { state.maxChunk = Infinity } else if (options.maxChunk === undefined) { state.maxChunk = transport.protocol.base.constants.DEFAULT_MAX_CHUNK } else {
state.maxChunk = options.maxChunk
}
// Connection-level flow control
var windowSize = options.windowSize || 1 << 20
state.window = new transport.Window({
id: 0,
isServer: state.isServer,
recv: {
size: state.constants.DEFAULT_WINDOW,
max: state.constants.MAX_INITIAL_WINDOW_SIZE
},
send: {
size: state.constants.DEFAULT_WINDOW,
max: state.constants.MAX_INITIAL_WINDOW_SIZE
}
})
// It starts with DEFAULT_WINDOW, update must be sent to change it on client
state.window.recv.setMax(windowSize)
// Boilerplate for Stream constructor
state.streamWindow = new transport.Window({
id: -1,
isServer: state.isServer,
recv: {
size: windowSize,
max: state.constants.MAX_INITIAL_WINDOW_SIZE
},
send: {
size: state.constants.DEFAULT_WINDOW,
max: state.constants.MAX_INITIAL_WINDOW_SIZE
}
})
// Various state info
state.pool = state.protocol.compressionPool.create(options.headerCompression)
state.counters = {
push: 0,
stream: 0
}
// Init streams list
state.stream = {
map: {},
count: 0,
nextId: state.isServer ? 2 : 1,
lastId: {
both: 0,
received: 0
}
}
state.ping = {
nextId: state.isServer ? 2 : 1,
map: {}
}
state.goaway = false
// Debug
state.debug = state.isServer ? debug.server : debug.client
// X-Forwarded feature
state.xForward = null
// Create parser and hole for framer
state.parser = state.protocol.parser.create({
// NOTE: needed to distinguish ping from ping ACK in SPDY
isServer: state.isServer,
window: state.window
})
state.framer = state.protocol.framer.create({
window: state.window,
timeout: state.timeout
})
// SPDY has PUSH enabled on servers
if (state.protocol.name === 'spdy') {
state.framer.enablePush(state.isServer)
}
if (!state.isServer) { state.parser.skipPreface() }
this.socket = socket
this._init()
}
util.inherits(Connection, EventEmitter)
exports.Connection = Connection
Connection.create = function create (socket, options) {
return new Connection(socket, options)
}
Connection.prototype._init = function init () {
var self = this
var state = this._spdyState
var pool = state.pool
// Initialize session window
state.window.recv.on('drain', function () {
self._onSessionWindowDrain()
})
// Initialize parser
state.parser.on('data', function (frame) {
self._handleFrame(frame)
})
state.parser.once('version', function (version) {
self._onVersion(version)
})
// Propagate parser errors
state.parser.on('error', function (err) {
self._onParserError(err)
})
// Propagate framer errors
state.framer.on('error', function (err) {
self.emit('error', err)
})
this.socket.pipe(state.parser)
state.framer.pipe(this.socket)
// Allow high-level api to catch socket errors
this.socket.on('error', function onSocketError (e) {
self.emit('error', e)
})
this.socket.once('close', function onclose (hadError) {
var err
if (hadError) {
err = new Error('socket hang up')
err.code = 'ECONNRESET'
}
self.destroyStreams(err)
self.emit('close')
if (state.pair) {
pool.put(state.pair)
}
state.framer.resume()
})
// Reset timeout on close
this.once('close', function () {
self.setTimeout(0)
})
function _onWindowOverflow () {
self._onWindowOverflow()
}
state.window.recv.on('overflow', _onWindowOverflow)
state.window.send.on('overflow', _onWindowOverflow)
// Do not allow half-open connections
this.socket.allowHalfOpen = false
}
Connection.prototype._onVersion = function _onVersion (version) {
var state = this._spdyState
var prev = state.version
var parser = state.parser
var framer = state.framer
var pool = state.pool
state.version = version
state.debug('id=0 version=%d', version)
// Ignore transition to 3.1
if (!prev) {
state.pair = pool.get(version)
parser.setCompression(state.pair)
framer.setCompression(state.pair)
}
framer.setVersion(version)
if (!state.isServer) {
framer.prefaceFrame()
if (state.xForward !== null) {
framer.xForwardedFor({ host: state.xForward })
}
}
// Send preface+settings frame (once)
framer.settingsFrame({
max_header_list_size: state.constants.DEFAULT_MAX_HEADER_LIST_SIZE,
max_concurrent_streams: state.maxStreams,
enable_push: state.acceptPush ? 1 : 0,
initial_window_size: state.window.recv.max
})
// Update session window
if (state.version >= 3.1 || (state.isServer && state.autoSpdy31)) { this._onSessionWindowDrain() }
this.emit('version', version)
}
Connection.prototype._onParserError = function _onParserError (err) {
var state = this._spdyState
// Prevent further errors
state.parser.kill()
// Send GOAWAY
if (err instanceof transport.protocol.base.utils.ProtocolError) {
this._goaway({
lastId: state.stream.lastId.both,
code: err.code,
extra: err.message,
send: true
})
}
this.emit('error', err)
}
Connection.prototype._handleFrame = function _handleFrame (frame) {
var state = this._spdyState
state.debug('id=0 frame', frame)
state.timeout.reset()
// For testing purposes
this.emit('frame', frame)
var stream
// Session window update
if (frame.type === 'WINDOW_UPDATE' && frame.id === 0) {
if (state.version < 3.1 && state.autoSpdy31) {
state.debug('id=0 switch version to 3.1')
state.version = 3.1
this.emit('version', 3.1)
}
state.window.send.update(frame.delta)
return
}
if (state.isServer && frame.type === 'PUSH_PROMISE') {
state.debug('id=0 server PUSH_PROMISE')
this._goaway({
lastId: state.stream.lastId.both,
code: 'PROTOCOL_ERROR',
send: true
})
return
}
if (!stream && frame.id !== undefined) {
// Load created one
stream = state.stream.map[frame.id]
// Fail if not found
if (!stream &&
frame.type !== 'HEADERS' &&
frame.type !== 'PRIORITY' &&
frame.type !== 'RST') {
// Other side should destroy the stream upon receiving GOAWAY
if (this._isGoaway(frame.id)) { return }
state.debug('id=0 stream=%d not found', frame.id)
state.framer.rstFrame({ id: frame.id, code: 'INVALID_STREAM' })
return
}
}
// Create new stream
if (!stream && frame.type === 'HEADERS') {
this._handleHeaders(frame)
return
}
if (stream) {
stream._handleFrame(frame)
} else if (frame.type === 'SETTINGS') {
this._handleSettings(frame.settings)
} else if (frame.type === 'ACK_SETTINGS') {
// TODO(indutny): handle it one day
} else if (frame.type === 'PING') {
this._handlePing(frame)
} else if (frame.type === 'GOAWAY') {
this._handleGoaway(frame)
} else if (frame.type === 'X_FORWARDED_FOR') {
// Set X-Forwarded-For only once
if (state.xForward === null) {
state.xForward = frame.host
}
} else if (frame.type === 'PRIORITY') {
// TODO(indutny): handle this
} else {
state.debug('id=0 unknown frame type: %s', frame.type)
}
}
Connection.prototype._onWindowOverflow = function _onWindowOverflow () {
var state = this._spdyState
state.debug('id=0 window overflow')
this._goaway({
lastId: state.stream.lastId.both,
code: 'FLOW_CONTROL_ERROR',
send: true
})
}
Connection.prototype._isGoaway = function _isGoaway (id) {
var state = this._spdyState
if (state.goaway !== false && state.goaway < id) { return true }
return false
}
Connection.prototype._getId = function _getId () {
var state = this._spdyState
var id = state.stream.nextId
state.stream.nextId += 2
return id
}
Connection.prototype._createStream = function _createStream (uri) {
var state = this._spdyState
var id = uri.id
if (id === undefined) { id = this._getId() }
var isGoaway = this._isGoaway(id)
if (uri.push && !state.acceptPush) {
state.debug('id=0 push disabled promisedId=%d', id)
// Fatal error
this._goaway({
lastId: state.stream.lastId.both,
code: 'PROTOCOL_ERROR',
send: true
})
isGoaway = true
}
var stream = new Stream(this, {
id: id,
request: uri.request !== false,
method: uri.method,
path: uri.path,
host: uri.host,
priority: uri.priority,
headers: uri.headers,
parent: uri.parent,
readable: !isGoaway && uri.readable,
writable: !isGoaway && uri.writable
})
var self = this
// Just an empty stream for API consistency
if (isGoaway) {
return stream
}
state.stream.lastId.both = Math.max(state.stream.lastId.both, id)
state.debug('id=0 add stream=%d', stream.id)
state.stream.map[stream.id] = stream
state.stream.count++
state.counters.stream++
if (stream.parent !== null) {
state.counters.push++
}
stream.once('close', function () {
self._removeStream(stream)
})
return stream
}
Connection.prototype._handleHeaders = function _handleHeaders (frame) {
var state = this._spdyState
// Must be HEADERS frame after stream close
if (frame.id <= state.stream.lastId.received) { return }
// Someone is using our ids!
if ((frame.id + state.stream.nextId) % 2 === 0) {
state.framer.rstFrame({ id: frame.id, code: 'PROTOCOL_ERROR' })
return
}
var stream = this._createStream({
id: frame.id,
request: false,
method: frame.headers[':method'],
path: frame.headers[':path'],
host: frame.headers[':authority'],
priority: frame.priority,
headers: frame.headers,
writable: frame.writable
})
// GOAWAY
if (this._isGoaway(stream.id)) {
return
}
state.stream.lastId.received = Math.max(
state.stream.lastId.received,
stream.id
)
// TODO(indutny) handle stream limit
if (!this.emit('stream', stream)) {
// No listeners was set - abort the stream
stream.abort()
return
}
// Create fake frame to simulate end of the data
if (frame.fin) {
stream._handleFrame({ type: 'FIN', fin: true })
}
return stream
}
Connection.prototype._onSessionWindowDrain = function _onSessionWindowDrain () {
var state = this._spdyState
if (state.version < 3.1 && !(state.isServer && state.autoSpdy31)) {
return
}
var delta = state.window.recv.getDelta()
if (delta === 0) {
return
}
state.debug('id=0 session window drain, update by %d', delta)
state.framer.windowUpdateFrame({
id: 0,
delta: delta
})
state.window.recv.update(delta)
}
Connection.prototype.start = function start (version) {
this._spdyState.parser.setVersion(version)
}
// Mostly for testing
Connection.prototype.getVersion = function getVersion () {
return this._spdyState.version
}
Connection.prototype._handleSettings = function _handleSettings (settings) {
var state = this._spdyState
state.framer.ackSettingsFrame()
this._setDefaultWindow(settings)
if (settings.max_frame_size) { state.framer.setMaxFrameSize(settings.max_frame_size) }
// TODO(indutny): handle max_header_list_size
if (settings.header_table_size) {
try {
state.pair.compress.updateTableSize(settings.header_table_size)
} catch (e) {
this._goaway({
lastId: 0,
code: 'PROTOCOL_ERROR',
send: true
})
return
}
}
// HTTP2 clients needs to enable PUSH streams explicitly
if (state.protocol.name !== 'spdy') {
if (settings.enable_push === undefined) {
state.framer.enablePush(state.isServer)
} else {
state.framer.enablePush(settings.enable_push === 1)
}
}
// TODO(indutny): handle max_concurrent_streams
}
Connection.prototype._setDefaultWindow = function _setDefaultWindow (settings) {
if (settings.initial_window_size === undefined) {
return
}
var state = this._spdyState
// Update defaults
var window = state.streamWindow
window.send.setMax(settings.initial_window_size)
// Update existing streams
Object.keys(state.stream.map).forEach(function (id) {
var stream = state.stream.map[id]
var window = stream._spdyState.window
window.send.updateMax(settings.initial_window_size)
})
}
Connection.prototype._handlePing = function handlePing (frame) {
var self = this
var state = this._spdyState
// Handle incoming PING
if (!frame.ack) {
state.framer.pingFrame({
opaque: frame.opaque,
ack: true
})
self.emit('ping', frame.opaque)
return
}
// Handle reply PING
var hex = frame.opaque.toString('hex')
if (!state.ping.map[hex]) {
return
}
var ping = state.ping.map[hex]
delete state.ping.map[hex]
if (ping.cb) {
ping.cb(null)
}
}
Connection.prototype._handleGoaway = function handleGoaway (frame) {
this._goaway({
lastId: frame.lastId,
code: frame.code,
send: false
})
}
Connection.prototype.ping = function ping (callback) {
var state = this._spdyState
// HTTP2 is using 8-byte opaque
var opaque = Buffer.alloc(state.constants.PING_OPAQUE_SIZE)
opaque.fill(0)
opaque.writeUInt32BE(state.ping.nextId, opaque.length - 4)
state.ping.nextId += 2
state.ping.map[opaque.toString('hex')] = { cb: callback }
state.framer.pingFrame({
opaque: opaque,
ack: false
})
}
Connection.prototype.getCounter = function getCounter (name) {
return this._spdyState.counters[name]
}
Connection.prototype.reserveStream = function reserveStream (uri, callback) {
var stream = this._createStream(uri)
// GOAWAY
if (this._isGoaway(stream.id)) {
var err = new Error('Can\'t send request after GOAWAY')
process.nextTick(function () {
if (callback) { callback(err) } else {
stream.emit('error', err)
}
})
return stream
}
if (callback) {
process.nextTick(function () {
callback(null, stream)
})
}
return stream
}
Connection.prototype.request = function request (uri, callback) {
var stream = this.reserveStream(uri, function (err) {
if (err) {
if (callback) {
callback(err)
} else {
stream.emit('error', err)
}
return
}
if (stream._wasSent()) {
if (callback) {
callback(null, stream)
}
return
}
stream.send(function (err) {
if (err) {
if (callback) { return callback(err) } else { return stream.emit('error', err) }
}
if (callback) {
callback(null, stream)
}
})
})
return stream
}
Connection.prototype._removeStream = function _removeStream (stream) {
var state = this._spdyState
state.debug('id=0 remove stream=%d', stream.id)
delete state.stream.map[stream.id]
state.stream.count--
if (state.stream.count === 0) {
this.emit('_streamDrain')
}
}
Connection.prototype._goaway = function _goaway (params) {
var state = this._spdyState
var self = this
state.goaway = params.lastId
state.debug('id=0 goaway from=%d', state.goaway)
Object.keys(state.stream.map).forEach(function (id) {
var stream = state.stream.map[id]
// Abort every stream started after GOAWAY
if (stream.id <= params.lastId) {
return
}
stream.abort()
stream.emit('error', new Error('New stream after GOAWAY'))
})
function finish () {
// Destroy socket if there are no streams
if (state.stream.count === 0 || params.code !== 'OK') {
// No further frames should be processed
state.parser.kill()
process.nextTick(function () {
var err = new Error('Fatal error: ' + params.code)
self._onStreamDrain(err)
})
return
}
self.on('_streamDrain', self._onStreamDrain)
}
if (params.send) {
// Make sure that GOAWAY frame is sent before dumping framer
state.framer.goawayFrame({
lastId: params.lastId,
code: params.code,
extra: params.extra
}, finish)
} else {
finish()
}
}
Connection.prototype._onStreamDrain = function _onStreamDrain (error) {
var state = this._spdyState
state.debug('id=0 _onStreamDrain')
state.framer.dump()
state.framer.unpipe(this.socket)
state.framer.resume()
if (this.socket.destroySoon) {
this.socket.destroySoon()
}
this.emit('close', error)
}
Connection.prototype.end = function end (callback) {
var state = this._spdyState
if (callback) {
this.once('close', callback)
}
this._goaway({
lastId: state.stream.lastId.both,
code: 'OK',
send: true
})
}
Connection.prototype.destroyStreams = function destroyStreams (err) {
var state = this._spdyState
Object.keys(state.stream.map).forEach(function (id) {
var stream = state.stream.map[id]
stream.destroy()
if (err) {
stream.emit('error', err)
}
})
}
Connection.prototype.isServer = function isServer () {
return this._spdyState.isServer
}
Connection.prototype.getXForwardedFor = function getXForwardFor () {
return this._spdyState.xForward
}
Connection.prototype.sendXForwardedFor = function sendXForwardedFor (host) {
var state = this._spdyState
if (state.version !== null) {
state.framer.xForwardedFor({ host: host })
} else {
state.xForward = host
}
}
Connection.prototype.pushPromise = function pushPromise (parent, uri, callback) {
var state = this._spdyState
var stream = this._createStream({
request: false,
parent: parent,
method: uri.method,
path: uri.path,
host: uri.host,
priority: uri.priority,
headers: uri.headers,
readable: false
})
var err
// TODO(indutny): deduplicate this logic somehow
if (this._isGoaway(stream.id)) {
err = new Error('Can\'t send PUSH_PROMISE after GOAWAY')
process.nextTick(function () {
if (callback) {
callback(err)
} else {
stream.emit('error', err)
}
})
return stream
}
if (uri.push && !state.acceptPush) {
err = new Error(
'Can\'t send PUSH_PROMISE, other side won\'t accept it')
process.nextTick(function () {
if (callback) { callback(err) } else {
stream.emit('error', err)
}
})
return stream
}
stream._sendPush(uri.status, uri.response, function (err) {
if (!callback) {
if (err) {
stream.emit('error', err)
}
return
}
if (err) { return callback(err) }
callback(null, stream)
})
return stream
}
Connection.prototype.setTimeout = function setTimeout (delay, callback) {
var state = this._spdyState
state.timeout.set(delay, callback)
}

View file

@ -0,0 +1,188 @@
'use strict'
var transport = require('../spdy-transport')
var utils = transport.utils
var assert = require('assert')
var debug = require('debug')('spdy:priority')
function PriorityNode (tree, options) {
this.tree = tree
this.id = options.id
this.parent = options.parent
this.weight = options.weight
// To be calculated in `addChild`
this.priorityFrom = 0
this.priorityTo = 1
this.priority = 1
this.children = {
list: [],
weight: 0
}
if (this.parent !== null) {
this.parent.addChild(this)
}
}
function compareChildren (a, b) {
return a.weight === b.weight ? a.id - b.id : a.weight - b.weight
}
PriorityNode.prototype.toJSON = function toJSON () {
return {
parent: this.parent,
weight: this.weight,
exclusive: this.exclusive
}
}
PriorityNode.prototype.getPriority = function getPriority () {
return this.priority
}
PriorityNode.prototype.getPriorityRange = function getPriorityRange () {
return { from: this.priorityFrom, to: this.priorityTo }
}
PriorityNode.prototype.addChild = function addChild (child) {
child.parent = this
utils.binaryInsert(this.children.list, child, compareChildren)
this.children.weight += child.weight
this._updatePriority(this.priorityFrom, this.priorityTo)
}
PriorityNode.prototype.remove = function remove () {
assert(this.parent, 'Can\'t remove root node')
this.parent.removeChild(this)
this.tree._removeNode(this)
// Move all children to the parent
for (var i = 0; i < this.children.list.length; i++) {
this.parent.addChild(this.children.list[i])
}
}
PriorityNode.prototype.removeChild = function removeChild (child) {
this.children.weight -= child.weight
var index = utils.binarySearch(this.children.list, child, compareChildren)
if (index !== -1 && this.children.list.length >= index) {
this.children.list.splice(index, 1)
}
}
PriorityNode.prototype.removeChildren = function removeChildren () {
var children = this.children.list
this.children.list = []
this.children.weight = 0
return children
}
PriorityNode.prototype._updatePriority = function _updatePriority (from, to) {
this.priority = to - from
this.priorityFrom = from
this.priorityTo = to
var weight = 0
for (var i = 0; i < this.children.list.length; i++) {
var node = this.children.list[i]
var nextWeight = weight + node.weight
node._updatePriority(
from + this.priority * (weight / this.children.weight),
from + this.priority * (nextWeight / this.children.weight)
)
weight = nextWeight
}
}
function PriorityTree (options) {
this.map = {}
this.list = []
this.defaultWeight = options.defaultWeight || 16
this.count = 0
this.maxCount = options.maxCount
// Root
this.root = this.add({
id: 0,
parent: null,
weight: 1
})
}
module.exports = PriorityTree
PriorityTree.create = function create (options) {
return new PriorityTree(options)
}
PriorityTree.prototype.add = function add (options) {
if (options.id === options.parent) {
return this.addDefault(options.id)
}
var parent = options.parent === null ? null : this.map[options.parent]
if (parent === undefined) {
return this.addDefault(options.id)
}
debug('add node=%d parent=%d weight=%d exclusive=%d',
options.id,
options.parent === null ? -1 : options.parent,
options.weight || this.defaultWeight,
options.exclusive ? 1 : 0)
var children
if (options.exclusive) {
children = parent.removeChildren()
}
var node = new PriorityNode(this, {
id: options.id,
parent: parent,
weight: options.weight || this.defaultWeight
})
this.map[options.id] = node
if (options.exclusive) {
for (var i = 0; i < children.length; i++) {
node.addChild(children[i])
}
}
this.count++
if (this.count > this.maxCount) {
debug('hit maximum remove id=%d', this.list[0].id)
this.list.shift().remove()
}
// Root node is not subject to removal
if (node.parent !== null) {
this.list.push(node)
}
return node
}
// Only for testing, should use `node`'s methods
PriorityTree.prototype.get = function get (id) {
return this.map[id]
}
PriorityTree.prototype.addDefault = function addDefault (id) {
debug('creating default node')
return this.add({ id: id, parent: 0, weight: this.defaultWeight })
}
PriorityTree.prototype._removeNode = function _removeNode (node) {
delete this.map[node.id]
var index = utils.binarySearch(this.list, node, compareChildren)
this.list.splice(index, 1)
this.count--
}

View file

@ -0,0 +1,4 @@
exports.DEFAULT_METHOD = 'GET'
exports.DEFAULT_HOST = 'localhost'
exports.MAX_PRIORITY_STREAMS = 100
exports.DEFAULT_MAX_CHUNK = 8 * 1024

View file

@ -0,0 +1,58 @@
'use strict'
var util = require('util')
var transport = require('../../../spdy-transport')
var base = require('./')
var Scheduler = base.Scheduler
function Framer (options) {
Scheduler.call(this)
this.version = null
this.compress = null
this.window = options.window
this.timeout = options.timeout
// Wait for `enablePush`
this.pushEnabled = null
}
util.inherits(Framer, Scheduler)
module.exports = Framer
Framer.prototype.setVersion = function setVersion (version) {
this.version = version
this.emit('version')
}
Framer.prototype.setCompression = function setCompresion (pair) {
this.compress = new transport.utils.LockStream(pair.compress)
}
Framer.prototype.enablePush = function enablePush (enable) {
this.pushEnabled = enable
this.emit('_pushEnabled')
}
Framer.prototype._checkPush = function _checkPush (callback) {
if (this.pushEnabled === null) {
this.once('_pushEnabled', function () {
this._checkPush(callback)
})
return
}
var err = null
if (!this.pushEnabled) {
err = new Error('PUSH_PROMISE disabled by other side')
}
process.nextTick(function () {
return callback(err)
})
}
Framer.prototype._resetTimeout = function _resetTimeout () {
if (this.timeout) {
this.timeout.reset()
}
}

View file

@ -0,0 +1,7 @@
'use strict'
exports.utils = require('./utils')
exports.constants = require('./constants')
exports.Scheduler = require('./scheduler')
exports.Parser = require('./parser')
exports.Framer = require('./framer')

View file

@ -0,0 +1,106 @@
'use strict'
var transport = require('../../../spdy-transport')
var util = require('util')
var utils = require('./').utils
var OffsetBuffer = require('obuf')
var Transform = require('readable-stream').Transform
function Parser (options) {
Transform.call(this, {
readableObjectMode: true
})
this.buffer = new OffsetBuffer()
this.partial = false
this.waiting = 0
this.window = options.window
this.version = null
this.decompress = null
this.dead = false
}
module.exports = Parser
util.inherits(Parser, Transform)
Parser.prototype.error = utils.error
Parser.prototype.kill = function kill () {
this.dead = true
}
Parser.prototype._transform = function transform (data, encoding, cb) {
if (!this.dead) { this.buffer.push(data) }
this._consume(cb)
}
Parser.prototype._consume = function _consume (cb) {
var self = this
function next (err, frame) {
if (err) {
return cb(err)
}
if (Array.isArray(frame)) {
for (var i = 0; i < frame.length; i++) {
self.push(frame[i])
}
} else if (frame) {
self.push(frame)
}
// Consume more packets
if (!sync) {
return self._consume(cb)
}
process.nextTick(function () {
self._consume(cb)
})
}
if (this.dead) {
return cb()
}
if (this.buffer.size < this.waiting) {
// No data at all
if (this.buffer.size === 0) {
return cb()
}
// Partial DATA frame or something that we can process partially
if (this.partial) {
var partial = this.buffer.clone(this.buffer.size)
this.buffer.skip(partial.size)
this.waiting -= partial.size
this.executePartial(partial, next)
return
}
// We shall not do anything until we get all expected data
return cb()
}
var sync = true
var content = this.buffer.clone(this.waiting)
this.buffer.skip(this.waiting)
this.execute(content, next)
sync = false
}
Parser.prototype.setVersion = function setVersion (version) {
this.version = version
this.emit('version', version)
}
Parser.prototype.setCompression = function setCompresion (pair) {
this.decompress = new transport.utils.LockStream(pair.decompress)
}

View file

@ -0,0 +1,216 @@
'use strict'
var transport = require('../../../spdy-transport')
var utils = transport.utils
var assert = require('assert')
var util = require('util')
var debug = require('debug')('spdy:scheduler')
var Readable = require('readable-stream').Readable
/*
* We create following structure in `pending`:
* [ [ id = 0 ], [ id = 1 ], [ id = 2 ], [ id = 0 ] ]
* chunks chunks chunks chunks
* chunks chunks
* chunks
*
* Then on the `.tick()` pass we pick one chunks from each item and remove the
* item if it is empty:
*
* [ [ id = 0 ], [ id = 2 ] ]
* chunks chunks
* chunks
*
* Writing out: chunks for 0, chunks for 1, chunks for 2, chunks for 0
*
* This way data is interleaved between the different streams.
*/
function Scheduler (options) {
Readable.call(this)
// Pretty big window by default
this.window = 0.25
if (options && options.window) { this.window = options.window }
this.sync = []
this.list = []
this.count = 0
this.pendingTick = false
}
util.inherits(Scheduler, Readable)
module.exports = Scheduler
// Just for testing, really
Scheduler.create = function create (options) {
return new Scheduler(options)
}
function insertCompare (a, b) {
return a.priority === b.priority
? a.stream - b.stream
: b.priority - a.priority
}
Scheduler.prototype.schedule = function schedule (data) {
var priority = data.priority
var stream = data.stream
var chunks = data.chunks
// Synchronous frames should not be interleaved
if (priority === false) {
debug('queue sync', chunks)
this.sync.push(data)
this.count += chunks.length
this._read()
return
}
debug('queue async priority=%d stream=%d', priority, stream, chunks)
var item = new SchedulerItem(stream, priority)
var index = utils.binaryLookup(this.list, item, insertCompare)
// Push new item
if (index >= this.list.length || insertCompare(this.list[index], item) !== 0) {
this.list.splice(index, 0, item)
} else { // Coalesce
item = this.list[index]
}
item.push(data)
this.count += chunks.length
this._read()
}
Scheduler.prototype._read = function _read () {
if (this.count === 0) {
return
}
if (this.pendingTick) {
return
}
this.pendingTick = true
var self = this
process.nextTick(function () {
self.pendingTick = false
self.tick()
})
}
Scheduler.prototype.tick = function tick () {
// No luck for async frames
if (!this.tickSync()) { return false }
return this.tickAsync()
}
Scheduler.prototype.tickSync = function tickSync () {
// Empty sync queue first
var sync = this.sync
var res = true
this.sync = []
for (var i = 0; i < sync.length; i++) {
var item = sync[i]
debug('tick sync pending=%d', this.count, item.chunks)
for (var j = 0; j < item.chunks.length; j++) {
this.count--
// TODO: handle stream backoff properly
try {
res = this.push(item.chunks[j])
} catch (err) {
this.emit('error', err)
return false
}
}
debug('after tick sync pending=%d', this.count)
// TODO(indutny): figure out the way to invoke callback on actual write
if (item.callback) {
item.callback(null)
}
}
return res
}
Scheduler.prototype.tickAsync = function tickAsync () {
var res = true
var list = this.list
if (list.length === 0) {
return res
}
var startPriority = list[0].priority
for (var index = 0; list.length > 0; index++) {
// Loop index
index %= list.length
if (startPriority - list[index].priority > this.window) { index = 0 }
debug('tick async index=%d start=%d', index, startPriority)
var current = list[index]
var item = current.shift()
if (current.isEmpty()) {
list.splice(index, 1)
if (index === 0 && list.length > 0) {
startPriority = list[0].priority
}
index--
}
debug('tick async pending=%d', this.count, item.chunks)
for (var i = 0; i < item.chunks.length; i++) {
this.count--
// TODO: handle stream backoff properly
try {
res = this.push(item.chunks[i])
} catch (err) {
this.emit('error', err)
return false
}
}
debug('after tick pending=%d', this.count)
// TODO(indutny): figure out the way to invoke callback on actual write
if (item.callback) {
item.callback(null)
}
if (!res) { break }
}
return res
}
Scheduler.prototype.dump = function dump () {
this.tickSync()
// Write everything out
while (!this.tickAsync()) {
// Intentional no-op
}
assert.strictEqual(this.count, 0)
}
function SchedulerItem (stream, priority) {
this.stream = stream
this.priority = priority
this.queue = []
}
SchedulerItem.prototype.push = function push (chunks) {
this.queue.push(chunks)
}
SchedulerItem.prototype.shift = function shift () {
return this.queue.shift()
}
SchedulerItem.prototype.isEmpty = function isEmpty () {
return this.queue.length === 0
}

View file

@ -0,0 +1,94 @@
'use strict'
var utils = exports
var util = require('util')
function ProtocolError (code, message) {
this.code = code
this.message = message
}
util.inherits(ProtocolError, Error)
utils.ProtocolError = ProtocolError
utils.error = function error (code, message) {
return new ProtocolError(code, message)
}
utils.reverse = function reverse (object) {
var result = []
Object.keys(object).forEach(function (key) {
result[object[key] | 0] = key
})
return result
}
// weight [1, 36] <=> priority [0, 7]
// This way weight=16 is preserved and has priority=3
utils.weightToPriority = function weightToPriority (weight) {
return ((Math.min(35, (weight - 1)) / 35) * 7) | 0
}
utils.priorityToWeight = function priorityToWeight (priority) {
return (((priority / 7) * 35) | 0) + 1
}
// Copy-Paste from node
exports.addHeaderLine = function addHeaderLine (field, value, dest) {
field = field.toLowerCase()
if (/^:/.test(field)) {
dest[field] = value
return
}
switch (field) {
// Array headers:
case 'set-cookie':
if (dest[field] !== undefined) {
dest[field].push(value)
} else {
dest[field] = [ value ]
}
break
/* eslint-disable max-len */
// list is taken from:
/* eslint-enable max-len */
case 'content-type':
case 'content-length':
case 'user-agent':
case 'referer':
case 'host':
case 'authorization':
case 'proxy-authorization':
case 'if-modified-since':
case 'if-unmodified-since':
case 'from':
case 'location':
case 'max-forwards':
// drop duplicates
if (dest[field] === undefined) {
dest[field] = value
}
break
case 'cookie':
// make semicolon-separated list
if (dest[field] !== undefined) {
dest[field] += '; ' + value
} else {
dest[field] = value
}
break
default:
// make comma-separated list
if (dest[field] !== undefined) {
dest[field] += ', ' + value
} else {
dest[field] = value
}
}
}

View file

@ -0,0 +1,93 @@
'use strict'
var transport = require('../../../spdy-transport')
var base = transport.protocol.base
exports.PREFACE_SIZE = 24
exports.PREFACE = 'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'
exports.PREFACE_BUFFER = Buffer.from(exports.PREFACE)
exports.PING_OPAQUE_SIZE = 8
exports.FRAME_HEADER_SIZE = 9
exports.INITIAL_MAX_FRAME_SIZE = 16384
exports.ABSOLUTE_MAX_FRAME_SIZE = 16777215
exports.HEADER_TABLE_SIZE = 4096
exports.DEFAULT_MAX_HEADER_LIST_SIZE = 80 * 1024 // as in http_parser
exports.MAX_INITIAL_WINDOW_SIZE = 2147483647
exports.DEFAULT_WEIGHT = 16
exports.MAX_CONCURRENT_STREAMS = Infinity
exports.frameType = {
DATA: 0,
HEADERS: 1,
PRIORITY: 2,
RST_STREAM: 3,
SETTINGS: 4,
PUSH_PROMISE: 5,
PING: 6,
GOAWAY: 7,
WINDOW_UPDATE: 8,
CONTINUATION: 9,
// Custom
X_FORWARDED_FOR: 0xde
}
exports.flags = {
ACK: 0x01, // SETTINGS-only
END_STREAM: 0x01,
END_HEADERS: 0x04,
PADDED: 0x08,
PRIORITY: 0x20
}
exports.settings = {
SETTINGS_HEADER_TABLE_SIZE: 0x01,
SETTINGS_ENABLE_PUSH: 0x02,
SETTINGS_MAX_CONCURRENT_STREAMS: 0x03,
SETTINGS_INITIAL_WINDOW_SIZE: 0x04,
SETTINGS_MAX_FRAME_SIZE: 0x05,
SETTINGS_MAX_HEADER_LIST_SIZE: 0x06
}
exports.settingsIndex = [
null,
'header_table_size',
'enable_push',
'max_concurrent_streams',
'initial_window_size',
'max_frame_size',
'max_header_list_size'
]
exports.error = {
OK: 0,
NO_ERROR: 0,
PROTOCOL_ERROR: 1,
INTERNAL_ERROR: 2,
FLOW_CONTROL_ERROR: 3,
SETTINGS_TIMEOUT: 4,
STREAM_CLOSED: 5,
INVALID_STREAM: 5,
FRAME_SIZE_ERROR: 6,
REFUSED_STREAM: 7,
CANCEL: 8,
COMPRESSION_ERROR: 9,
CONNECT_ERROR: 10,
ENHANCE_YOUR_CALM: 11,
INADEQUATE_SECURITY: 12,
HTTP_1_1_REQUIRED: 13
}
exports.errorByCode = base.utils.reverse(exports.error)
exports.DEFAULT_WINDOW = 64 * 1024 - 1
exports.goaway = exports.error
exports.goawayByCode = Object.assign({}, exports.errorByCode)
exports.goawayByCode[0] = 'OK'

View file

@ -0,0 +1,542 @@
'use strict'
var transport = require('../../../spdy-transport')
var base = transport.protocol.base
var constants = require('./').constants
var assert = require('assert')
var util = require('util')
var WriteBuffer = require('wbuf')
var OffsetBuffer = require('obuf')
var debug = require('debug')('spdy:framer')
var debugExtra = require('debug')('spdy:framer:extra')
function Framer (options) {
base.Framer.call(this, options)
this.maxFrameSize = constants.INITIAL_MAX_FRAME_SIZE
}
util.inherits(Framer, base.Framer)
module.exports = Framer
Framer.create = function create (options) {
return new Framer(options)
}
Framer.prototype.setMaxFrameSize = function setMaxFrameSize (size) {
this.maxFrameSize = size
}
Framer.prototype._frame = function _frame (frame, body, callback) {
debug('id=%d type=%s', frame.id, frame.type)
var buffer = new WriteBuffer()
buffer.reserve(constants.FRAME_HEADER_SIZE)
var len = buffer.skip(3)
buffer.writeUInt8(constants.frameType[frame.type])
buffer.writeUInt8(frame.flags)
buffer.writeUInt32BE(frame.id & 0x7fffffff)
body(buffer)
var frameSize = buffer.size - constants.FRAME_HEADER_SIZE
len.writeUInt24BE(frameSize)
var chunks = buffer.render()
var toWrite = {
stream: frame.id,
priority: frame.priority === undefined ? false : frame.priority,
chunks: chunks,
callback: callback
}
if (this.window && frame.type === 'DATA') {
var self = this
this._resetTimeout()
this.window.send.update(-frameSize, function () {
self._resetTimeout()
self.schedule(toWrite)
})
} else {
this._resetTimeout()
this.schedule(toWrite)
}
return chunks
}
Framer.prototype._split = function _split (frame) {
var buf = new OffsetBuffer()
for (var i = 0; i < frame.chunks.length; i++) { buf.push(frame.chunks[i]) }
var frames = []
while (!buf.isEmpty()) {
// First frame may have reserved bytes in it
var size = this.maxFrameSize
if (frames.length === 0) {
size -= frame.reserve
}
size = Math.min(size, buf.size)
var frameBuf = buf.clone(size)
buf.skip(size)
frames.push({
size: frameBuf.size,
chunks: frameBuf.toChunks()
})
}
return frames
}
Framer.prototype._continuationFrame = function _continuationFrame (frame,
body,
callback) {
var frames = this._split(frame)
frames.forEach(function (subFrame, i) {
var isFirst = i === 0
var isLast = i === frames.length - 1
var flags = isLast ? constants.flags.END_HEADERS : 0
// PRIORITY and friends
if (isFirst) {
flags |= frame.flags
}
this._frame({
id: frame.id,
priority: false,
type: isFirst ? frame.type : 'CONTINUATION',
flags: flags
}, function (buf) {
// Fill those reserved bytes
if (isFirst && body) { body(buf) }
buf.reserve(subFrame.size)
for (var i = 0; i < subFrame.chunks.length; i++) { buf.copyFrom(subFrame.chunks[i]) }
}, isLast ? callback : null)
}, this)
if (frames.length === 0) {
this._frame({
id: frame.id,
priority: false,
type: frame.type,
flags: frame.flags | constants.flags.END_HEADERS
}, function (buf) {
if (body) { body(buf) }
}, callback)
}
}
Framer.prototype._compressHeaders = function _compressHeaders (headers,
pairs,
callback) {
Object.keys(headers || {}).forEach(function (name) {
var lowName = name.toLowerCase()
// Not allowed in HTTP2
switch (lowName) {
case 'host':
case 'connection':
case 'keep-alive':
case 'proxy-connection':
case 'transfer-encoding':
case 'upgrade':
return
}
// Should be in `pairs`
if (/^:/.test(lowName)) {
return
}
// Do not compress, or index Cookie field (for security reasons)
var neverIndex = lowName === 'cookie' || lowName === 'set-cookie'
var value = headers[name]
if (Array.isArray(value)) {
for (var i = 0; i < value.length; i++) {
pairs.push({
name: lowName,
value: value[i] + '',
neverIndex: neverIndex,
huffman: !neverIndex
})
}
} else {
pairs.push({
name: lowName,
value: value + '',
neverIndex: neverIndex,
huffman: !neverIndex
})
}
})
assert(this.compress !== null, 'Framer version not initialized')
debugExtra('compressing headers=%j', pairs)
this.compress.write([ pairs ], callback)
}
Framer.prototype._isDefaultPriority = function _isDefaultPriority (priority) {
if (!priority) { return true }
return !priority.parent &&
priority.weight === constants.DEFAULT &&
!priority.exclusive
}
Framer.prototype._defaultHeaders = function _defaultHeaders (frame, pairs) {
if (!frame.path) {
throw new Error('`path` is required frame argument')
}
pairs.push({
name: ':method',
value: frame.method || base.constants.DEFAULT_METHOD
})
pairs.push({ name: ':path', value: frame.path })
pairs.push({ name: ':scheme', value: frame.scheme || 'https' })
pairs.push({
name: ':authority',
value: frame.host ||
(frame.headers && frame.headers.host) ||
base.constants.DEFAULT_HOST
})
}
Framer.prototype._headersFrame = function _headersFrame (kind, frame, callback) {
var pairs = []
if (kind === 'request') {
this._defaultHeaders(frame, pairs)
} else if (kind === 'response') {
pairs.push({ name: ':status', value: (frame.status || 200) + '' })
}
var self = this
this._compressHeaders(frame.headers, pairs, function (err, chunks) {
if (err) {
if (callback) {
return callback(err)
} else {
return self.emit('error', err)
}
}
var reserve = 0
// If priority info is present, and the values are not default ones
// reserve space for the priority info and add PRIORITY flag
var priority = frame.priority
if (!self._isDefaultPriority(priority)) { reserve = 5 }
var flags = reserve === 0 ? 0 : constants.flags.PRIORITY
// Mostly for testing
if (frame.fin) {
flags |= constants.flags.END_STREAM
}
self._continuationFrame({
id: frame.id,
type: 'HEADERS',
flags: flags,
reserve: reserve,
chunks: chunks
}, function (buf) {
if (reserve === 0) {
return
}
buf.writeUInt32BE(((priority.exclusive ? 0x80000000 : 0) |
priority.parent) >>> 0)
buf.writeUInt8((priority.weight | 0) - 1)
}, callback)
})
}
Framer.prototype.requestFrame = function requestFrame (frame, callback) {
return this._headersFrame('request', frame, callback)
}
Framer.prototype.responseFrame = function responseFrame (frame, callback) {
return this._headersFrame('response', frame, callback)
}
Framer.prototype.headersFrame = function headersFrame (frame, callback) {
return this._headersFrame('headers', frame, callback)
}
Framer.prototype.pushFrame = function pushFrame (frame, callback) {
var self = this
function compress (headers, pairs, callback) {
self._compressHeaders(headers, pairs, function (err, chunks) {
if (err) {
if (callback) {
return callback(err)
} else {
return self.emit('error', err)
}
}
callback(chunks)
})
}
function sendPromise (chunks) {
self._continuationFrame({
id: frame.id,
type: 'PUSH_PROMISE',
reserve: 4,
chunks: chunks
}, function (buf) {
buf.writeUInt32BE(frame.promisedId)
})
}
function sendResponse (chunks, callback) {
var priority = frame.priority
var isDefaultPriority = self._isDefaultPriority(priority)
var flags = isDefaultPriority ? 0 : constants.flags.PRIORITY
// Mostly for testing
if (frame.fin) {
flags |= constants.flags.END_STREAM
}
self._continuationFrame({
id: frame.promisedId,
type: 'HEADERS',
flags: flags,
reserve: isDefaultPriority ? 0 : 5,
chunks: chunks
}, function (buf) {
if (isDefaultPriority) {
return
}
buf.writeUInt32BE((priority.exclusive ? 0x80000000 : 0) |
priority.parent)
buf.writeUInt8((priority.weight | 0) - 1)
}, callback)
}
this._checkPush(function (err) {
if (err) {
return callback(err)
}
var pairs = {
promise: [],
response: []
}
self._defaultHeaders(frame, pairs.promise)
pairs.response.push({ name: ':status', value: (frame.status || 200) + '' })
compress(frame.headers, pairs.promise, function (promiseChunks) {
sendPromise(promiseChunks)
if (frame.response === false) {
return callback(null)
}
compress(frame.response, pairs.response, function (responseChunks) {
sendResponse(responseChunks, callback)
})
})
})
}
Framer.prototype.priorityFrame = function priorityFrame (frame, callback) {
this._frame({
id: frame.id,
priority: false,
type: 'PRIORITY',
flags: 0
}, function (buf) {
var priority = frame.priority
buf.writeUInt32BE((priority.exclusive ? 0x80000000 : 0) |
priority.parent)
buf.writeUInt8((priority.weight | 0) - 1)
}, callback)
}
Framer.prototype.dataFrame = function dataFrame (frame, callback) {
var frames = this._split({
reserve: 0,
chunks: [ frame.data ]
})
var fin = frame.fin ? constants.flags.END_STREAM : 0
var self = this
frames.forEach(function (subFrame, i) {
var isLast = i === frames.length - 1
var flags = 0
if (isLast) {
flags |= fin
}
self._frame({
id: frame.id,
priority: frame.priority,
type: 'DATA',
flags: flags
}, function (buf) {
buf.reserve(subFrame.size)
for (var i = 0; i < subFrame.chunks.length; i++) { buf.copyFrom(subFrame.chunks[i]) }
}, isLast ? callback : null)
})
// Empty DATA
if (frames.length === 0) {
this._frame({
id: frame.id,
priority: frame.priority,
type: 'DATA',
flags: fin
}, function (buf) {
// No-op
}, callback)
}
}
Framer.prototype.pingFrame = function pingFrame (frame, callback) {
this._frame({
id: 0,
type: 'PING',
flags: frame.ack ? constants.flags.ACK : 0
}, function (buf) {
buf.copyFrom(frame.opaque)
}, callback)
}
Framer.prototype.rstFrame = function rstFrame (frame, callback) {
this._frame({
id: frame.id,
type: 'RST_STREAM',
flags: 0
}, function (buf) {
buf.writeUInt32BE(constants.error[frame.code])
}, callback)
}
Framer.prototype.prefaceFrame = function prefaceFrame (callback) {
debug('preface')
this._resetTimeout()
this.schedule({
stream: 0,
priority: false,
chunks: [ constants.PREFACE_BUFFER ],
callback: callback
})
}
Framer.prototype.settingsFrame = function settingsFrame (options, callback) {
var key = JSON.stringify(options)
var settings = Framer.settingsCache[key]
if (settings) {
debug('cached settings')
this._resetTimeout()
this.schedule({
id: 0,
priority: false,
chunks: settings,
callback: callback
})
return
}
var params = []
for (var i = 0; i < constants.settingsIndex.length; i++) {
var name = constants.settingsIndex[i]
if (!name) {
continue
}
// value: Infinity
if (!isFinite(options[name])) {
continue
}
if (options[name] !== undefined) {
params.push({ key: i, value: options[name] })
}
}
var bodySize = params.length * 6
var chunks = this._frame({
id: 0,
type: 'SETTINGS',
flags: 0
}, function (buffer) {
buffer.reserve(bodySize)
for (var i = 0; i < params.length; i++) {
var param = params[i]
buffer.writeUInt16BE(param.key)
buffer.writeUInt32BE(param.value)
}
}, callback)
Framer.settingsCache[key] = chunks
}
Framer.settingsCache = {}
Framer.prototype.ackSettingsFrame = function ackSettingsFrame (callback) {
/* var chunks = */ this._frame({
id: 0,
type: 'SETTINGS',
flags: constants.flags.ACK
}, function (buffer) {
// No-op
}, callback)
}
Framer.prototype.windowUpdateFrame = function windowUpdateFrame (frame,
callback) {
this._frame({
id: frame.id,
type: 'WINDOW_UPDATE',
flags: 0
}, function (buffer) {
buffer.reserve(4)
buffer.writeInt32BE(frame.delta)
}, callback)
}
Framer.prototype.goawayFrame = function goawayFrame (frame, callback) {
this._frame({
type: 'GOAWAY',
id: 0,
flags: 0
}, function (buf) {
buf.reserve(8)
// Last-good-stream-ID
buf.writeUInt32BE(frame.lastId & 0x7fffffff)
// Code
buf.writeUInt32BE(constants.goaway[frame.code])
// Extra debugging information
if (frame.extra) { buf.write(frame.extra) }
}, callback)
}
Framer.prototype.xForwardedFor = function xForwardedFor (frame, callback) {
this._frame({
type: 'X_FORWARDED_FOR',
id: 0,
flags: 0
}, function (buf) {
buf.write(frame.host)
}, callback)
}

View file

@ -0,0 +1,34 @@
'use strict'
var constants = require('./').constants
var hpack = require('hpack.js')
function Pool () {
}
module.exports = Pool
Pool.create = function create () {
return new Pool()
}
Pool.prototype.get = function get (version) {
var options = {
table: {
maxSize: constants.HEADER_TABLE_SIZE
}
}
var compress = hpack.compressor.create(options)
var decompress = hpack.decompressor.create(options)
return {
version: version,
compress: compress,
decompress: decompress
}
}
Pool.prototype.put = function put () {
}

View file

@ -0,0 +1,8 @@
'use strict'
exports.name = 'h2'
exports.constants = require('./constants')
exports.parser = require('./parser')
exports.framer = require('./framer')
exports.compressionPool = require('./hpack-pool')

View file

@ -0,0 +1,578 @@
'use strict'
var parser = exports
var transport = require('../../../spdy-transport')
var base = transport.protocol.base
var utils = base.utils
var constants = require('./').constants
var assert = require('assert')
var util = require('util')
function Parser (options) {
base.Parser.call(this, options)
this.isServer = options.isServer
this.waiting = constants.PREFACE_SIZE
this.state = 'preface'
this.pendingHeader = null
// Header Block queue
this._lastHeaderBlock = null
this.maxFrameSize = constants.INITIAL_MAX_FRAME_SIZE
this.maxHeaderListSize = constants.DEFAULT_MAX_HEADER_LIST_SIZE
}
util.inherits(Parser, base.Parser)
parser.create = function create (options) {
return new Parser(options)
}
Parser.prototype.setMaxFrameSize = function setMaxFrameSize (size) {
this.maxFrameSize = size
}
Parser.prototype.setMaxHeaderListSize = function setMaxHeaderListSize (size) {
this.maxHeaderListSize = size
}
// Only for testing
Parser.prototype.skipPreface = function skipPreface () {
// Just some number bigger than 3.1, doesn't really matter for HTTP2
this.setVersion(4)
// Parse frame header!
this.state = 'frame-head'
this.waiting = constants.FRAME_HEADER_SIZE
}
Parser.prototype.execute = function execute (buffer, callback) {
if (this.state === 'preface') { return this.onPreface(buffer, callback) }
if (this.state === 'frame-head') {
return this.onFrameHead(buffer, callback)
}
assert(this.state === 'frame-body' && this.pendingHeader !== null)
var self = this
var header = this.pendingHeader
this.pendingHeader = null
this.onFrameBody(header, buffer, function (err, frame) {
if (err) {
return callback(err)
}
self.state = 'frame-head'
self.partial = false
self.waiting = constants.FRAME_HEADER_SIZE
callback(null, frame)
})
}
Parser.prototype.executePartial = function executePartial (buffer, callback) {
var header = this.pendingHeader
assert.strictEqual(header.flags & constants.flags.PADDED, 0)
if (this.window) { this.window.recv.update(-buffer.size) }
callback(null, {
type: 'DATA',
id: header.id,
// Partial DATA can't be FIN
fin: false,
data: buffer.take(buffer.size)
})
}
Parser.prototype.onPreface = function onPreface (buffer, callback) {
if (buffer.take(buffer.size).toString() !== constants.PREFACE) {
return callback(this.error(constants.error.PROTOCOL_ERROR,
'Invalid preface'))
}
this.skipPreface()
callback(null, null)
}
Parser.prototype.onFrameHead = function onFrameHead (buffer, callback) {
var header = {
length: buffer.readUInt24BE(),
control: true,
type: buffer.readUInt8(),
flags: buffer.readUInt8(),
id: buffer.readUInt32BE() & 0x7fffffff
}
if (header.length > this.maxFrameSize) {
return callback(this.error(constants.error.FRAME_SIZE_ERROR,
'Frame length OOB'))
}
header.control = header.type !== constants.frameType.DATA
this.state = 'frame-body'
this.pendingHeader = header
this.waiting = header.length
this.partial = !header.control
// TODO(indutny): eventually support partial padded DATA
if (this.partial) {
this.partial = (header.flags & constants.flags.PADDED) === 0
}
callback(null, null)
}
Parser.prototype.onFrameBody = function onFrameBody (header, buffer, callback) {
var frameType = constants.frameType
if (header.type === frameType.DATA) {
this.onDataFrame(header, buffer, callback)
} else if (header.type === frameType.HEADERS) {
this.onHeadersFrame(header, buffer, callback)
} else if (header.type === frameType.CONTINUATION) {
this.onContinuationFrame(header, buffer, callback)
} else if (header.type === frameType.WINDOW_UPDATE) {
this.onWindowUpdateFrame(header, buffer, callback)
} else if (header.type === frameType.RST_STREAM) {
this.onRSTFrame(header, buffer, callback)
} else if (header.type === frameType.SETTINGS) {
this.onSettingsFrame(header, buffer, callback)
} else if (header.type === frameType.PUSH_PROMISE) {
this.onPushPromiseFrame(header, buffer, callback)
} else if (header.type === frameType.PING) {
this.onPingFrame(header, buffer, callback)
} else if (header.type === frameType.GOAWAY) {
this.onGoawayFrame(header, buffer, callback)
} else if (header.type === frameType.PRIORITY) {
this.onPriorityFrame(header, buffer, callback)
} else if (header.type === frameType.X_FORWARDED_FOR) {
this.onXForwardedFrame(header, buffer, callback)
} else {
this.onUnknownFrame(header, buffer, callback)
}
}
Parser.prototype.onUnknownFrame = function onUnknownFrame (header, buffer, callback) {
if (this._lastHeaderBlock !== null) {
callback(this.error(constants.error.PROTOCOL_ERROR,
'Received unknown frame in the middle of a header block'))
return
}
callback(null, { type: 'unknown: ' + header.type })
}
Parser.prototype.unpadData = function unpadData (header, body, callback) {
var isPadded = (header.flags & constants.flags.PADDED) !== 0
if (!isPadded) { return callback(null, body) }
if (!body.has(1)) {
return callback(this.error(constants.error.FRAME_SIZE_ERROR,
'Not enough space for padding'))
}
var pad = body.readUInt8()
if (!body.has(pad)) {
return callback(this.error(constants.error.PROTOCOL_ERROR,
'Invalid padding size'))
}
var contents = body.clone(body.size - pad)
body.skip(body.size)
callback(null, contents)
}
Parser.prototype.onDataFrame = function onDataFrame (header, body, callback) {
var isEndStream = (header.flags & constants.flags.END_STREAM) !== 0
if (header.id === 0) {
return callback(this.error(constants.error.PROTOCOL_ERROR,
'Received DATA frame with stream=0'))
}
// Count received bytes
if (this.window) {
this.window.recv.update(-body.size)
}
this.unpadData(header, body, function (err, data) {
if (err) {
return callback(err)
}
callback(null, {
type: 'DATA',
id: header.id,
fin: isEndStream,
data: data.take(data.size)
})
})
}
Parser.prototype.initHeaderBlock = function initHeaderBlock (header,
frame,
block,
callback) {
if (this._lastHeaderBlock) {
return callback(this.error(constants.error.PROTOCOL_ERROR,
'Duplicate Stream ID'))
}
this._lastHeaderBlock = {
id: header.id,
frame: frame,
queue: [],
size: 0
}
this.queueHeaderBlock(header, block, callback)
}
Parser.prototype.queueHeaderBlock = function queueHeaderBlock (header,
block,
callback) {
var self = this
var item = this._lastHeaderBlock
if (!this._lastHeaderBlock || item.id !== header.id) {
return callback(this.error(constants.error.PROTOCOL_ERROR,
'No matching stream for continuation'))
}
var fin = (header.flags & constants.flags.END_HEADERS) !== 0
var chunks = block.toChunks()
for (var i = 0; i < chunks.length; i++) {
var chunk = chunks[i]
item.queue.push(chunk)
item.size += chunk.length
}
if (item.size >= self.maxHeaderListSize) {
return callback(this.error(constants.error.PROTOCOL_ERROR,
'Compressed header list is too large'))
}
if (!fin) { return callback(null, null) }
this._lastHeaderBlock = null
this.decompress.write(item.queue, function (err, chunks) {
if (err) {
return callback(self.error(constants.error.COMPRESSION_ERROR,
err.message))
}
var headers = {}
var size = 0
for (var i = 0; i < chunks.length; i++) {
var header = chunks[i]
size += header.name.length + header.value.length + 32
if (size >= self.maxHeaderListSize) {
return callback(self.error(constants.error.PROTOCOL_ERROR,
'Header list is too large'))
}
if (/[A-Z]/.test(header.name)) {
return callback(self.error(constants.error.PROTOCOL_ERROR,
'Header name must be lowercase'))
}
utils.addHeaderLine(header.name, header.value, headers)
}
item.frame.headers = headers
item.frame.path = headers[':path']
callback(null, item.frame)
})
}
Parser.prototype.onHeadersFrame = function onHeadersFrame (header,
body,
callback) {
var self = this
if (header.id === 0) {
return callback(this.error(constants.error.PROTOCOL_ERROR,
'Invalid stream id for HEADERS'))
}
this.unpadData(header, body, function (err, data) {
if (err) { return callback(err) }
var isPriority = (header.flags & constants.flags.PRIORITY) !== 0
if (!data.has(isPriority ? 5 : 0)) {
return callback(self.error(constants.error.FRAME_SIZE_ERROR,
'Not enough data for HEADERS'))
}
var exclusive = false
var dependency = 0
var weight = constants.DEFAULT_WEIGHT
if (isPriority) {
dependency = data.readUInt32BE()
exclusive = (dependency & 0x80000000) !== 0
dependency &= 0x7fffffff
// Weight's range is [1, 256]
weight = data.readUInt8() + 1
}
if (dependency === header.id) {
return callback(self.error(constants.error.PROTOCOL_ERROR,
'Stream can\'t dependend on itself'))
}
var streamInfo = {
type: 'HEADERS',
id: header.id,
priority: {
parent: dependency,
exclusive: exclusive,
weight: weight
},
fin: (header.flags & constants.flags.END_STREAM) !== 0,
writable: true,
headers: null,
path: null
}
self.initHeaderBlock(header, streamInfo, data, callback)
})
}
Parser.prototype.onContinuationFrame = function onContinuationFrame (header,
body,
callback) {
this.queueHeaderBlock(header, body, callback)
}
Parser.prototype.onRSTFrame = function onRSTFrame (header, body, callback) {
if (body.size !== 4) {
return callback(this.error(constants.error.FRAME_SIZE_ERROR,
'RST_STREAM length not 4'))
}
if (header.id === 0) {
return callback(this.error(constants.error.PROTOCOL_ERROR,
'Invalid stream id for RST_STREAM'))
}
callback(null, {
type: 'RST',
id: header.id,
code: constants.errorByCode[body.readUInt32BE()]
})
}
Parser.prototype._validateSettings = function _validateSettings (settings) {
if (settings['enable_push'] !== undefined &&
settings['enable_push'] !== 0 &&
settings['enable_push'] !== 1) {
return this.error(constants.error.PROTOCOL_ERROR,
'SETTINGS_ENABLE_PUSH must be 0 or 1')
}
if (settings['initial_window_size'] !== undefined &&
(settings['initial_window_size'] > constants.MAX_INITIAL_WINDOW_SIZE ||
settings['initial_window_size'] < 0)) {
return this.error(constants.error.FLOW_CONTROL_ERROR,
'SETTINGS_INITIAL_WINDOW_SIZE is OOB')
}
if (settings['max_frame_size'] !== undefined &&
(settings['max_frame_size'] > constants.ABSOLUTE_MAX_FRAME_SIZE ||
settings['max_frame_size'] < constants.INITIAL_MAX_FRAME_SIZE)) {
return this.error(constants.error.PROTOCOL_ERROR,
'SETTINGS_MAX_FRAME_SIZE is OOB')
}
return undefined
}
Parser.prototype.onSettingsFrame = function onSettingsFrame (header,
body,
callback) {
if (header.id !== 0) {
return callback(this.error(constants.error.PROTOCOL_ERROR,
'Invalid stream id for SETTINGS'))
}
var isAck = (header.flags & constants.flags.ACK) !== 0
if (isAck && body.size !== 0) {
return callback(this.error(constants.error.FRAME_SIZE_ERROR,
'SETTINGS with ACK and non-zero length'))
}
if (isAck) {
return callback(null, { type: 'ACK_SETTINGS' })
}
if (body.size % 6 !== 0) {
return callback(this.error(constants.error.FRAME_SIZE_ERROR,
'SETTINGS length not multiple of 6'))
}
var settings = {}
while (!body.isEmpty()) {
var id = body.readUInt16BE()
var value = body.readUInt32BE()
var name = constants.settingsIndex[id]
if (name) {
settings[name] = value
}
}
var err = this._validateSettings(settings)
if (err !== undefined) {
return callback(err)
}
callback(null, {
type: 'SETTINGS',
settings: settings
})
}
Parser.prototype.onPushPromiseFrame = function onPushPromiseFrame (header,
body,
callback) {
if (header.id === 0) {
return callback(this.error(constants.error.PROTOCOL_ERROR,
'Invalid stream id for PUSH_PROMISE'))
}
var self = this
this.unpadData(header, body, function (err, data) {
if (err) {
return callback(err)
}
if (!data.has(4)) {
return callback(self.error(constants.error.FRAME_SIZE_ERROR,
'PUSH_PROMISE length less than 4'))
}
var streamInfo = {
type: 'PUSH_PROMISE',
id: header.id,
fin: false,
promisedId: data.readUInt32BE() & 0x7fffffff,
headers: null,
path: null
}
self.initHeaderBlock(header, streamInfo, data, callback)
})
}
Parser.prototype.onPingFrame = function onPingFrame (header, body, callback) {
if (body.size !== 8) {
return callback(this.error(constants.error.FRAME_SIZE_ERROR,
'PING length != 8'))
}
if (header.id !== 0) {
return callback(this.error(constants.error.PROTOCOL_ERROR,
'Invalid stream id for PING'))
}
var ack = (header.flags & constants.flags.ACK) !== 0
callback(null, { type: 'PING', opaque: body.take(body.size), ack: ack })
}
Parser.prototype.onGoawayFrame = function onGoawayFrame (header,
body,
callback) {
if (!body.has(8)) {
return callback(this.error(constants.error.FRAME_SIZE_ERROR,
'GOAWAY length < 8'))
}
if (header.id !== 0) {
return callback(this.error(constants.error.PROTOCOL_ERROR,
'Invalid stream id for GOAWAY'))
}
var frame = {
type: 'GOAWAY',
lastId: body.readUInt32BE(),
code: constants.goawayByCode[body.readUInt32BE()]
}
if (body.size !== 0) { frame.debug = body.take(body.size) }
callback(null, frame)
}
Parser.prototype.onPriorityFrame = function onPriorityFrame (header,
body,
callback) {
if (body.size !== 5) {
return callback(this.error(constants.error.FRAME_SIZE_ERROR,
'PRIORITY length != 5'))
}
if (header.id === 0) {
return callback(this.error(constants.error.PROTOCOL_ERROR,
'Invalid stream id for PRIORITY'))
}
var dependency = body.readUInt32BE()
// Again the range is from 1 to 256
var weight = body.readUInt8() + 1
if (dependency === header.id) {
return callback(this.error(constants.error.PROTOCOL_ERROR,
'Stream can\'t dependend on itself'))
}
callback(null, {
type: 'PRIORITY',
id: header.id,
priority: {
exclusive: (dependency & 0x80000000) !== 0,
parent: dependency & 0x7fffffff,
weight: weight
}
})
}
Parser.prototype.onWindowUpdateFrame = function onWindowUpdateFrame (header,
body,
callback) {
if (body.size !== 4) {
return callback(this.error(constants.error.FRAME_SIZE_ERROR,
'WINDOW_UPDATE length != 4'))
}
var delta = body.readInt32BE()
if (delta === 0) {
return callback(this.error(constants.error.PROTOCOL_ERROR,
'WINDOW_UPDATE delta == 0'))
}
callback(null, {
type: 'WINDOW_UPDATE',
id: header.id,
delta: delta
})
}
Parser.prototype.onXForwardedFrame = function onXForwardedFrame (header,
body,
callback) {
callback(null, {
type: 'X_FORWARDED_FOR',
host: body.take(body.size).toString()
})
}

View file

@ -0,0 +1,146 @@
'use strict'
var transport = require('../../../spdy-transport')
var base = transport.protocol.base
exports.FRAME_HEADER_SIZE = 8
exports.PING_OPAQUE_SIZE = 4
exports.MAX_CONCURRENT_STREAMS = Infinity
exports.DEFAULT_MAX_HEADER_LIST_SIZE = Infinity
exports.DEFAULT_WEIGHT = 16
exports.frameType = {
SYN_STREAM: 1,
SYN_REPLY: 2,
RST_STREAM: 3,
SETTINGS: 4,
PING: 6,
GOAWAY: 7,
HEADERS: 8,
WINDOW_UPDATE: 9,
// Custom
X_FORWARDED_FOR: 0xf000
}
exports.flags = {
FLAG_FIN: 0x01,
FLAG_COMPRESSED: 0x02,
FLAG_UNIDIRECTIONAL: 0x02
}
exports.error = {
PROTOCOL_ERROR: 1,
INVALID_STREAM: 2,
REFUSED_STREAM: 3,
UNSUPPORTED_VERSION: 4,
CANCEL: 5,
INTERNAL_ERROR: 6,
FLOW_CONTROL_ERROR: 7,
STREAM_IN_USE: 8,
// STREAM_ALREADY_CLOSED: 9
STREAM_CLOSED: 9,
INVALID_CREDENTIALS: 10,
FRAME_TOO_LARGE: 11
}
exports.errorByCode = base.utils.reverse(exports.error)
exports.settings = {
FLAG_SETTINGS_PERSIST_VALUE: 1,
FLAG_SETTINGS_PERSISTED: 2,
SETTINGS_UPLOAD_BANDWIDTH: 1,
SETTINGS_DOWNLOAD_BANDWIDTH: 2,
SETTINGS_ROUND_TRIP_TIME: 3,
SETTINGS_MAX_CONCURRENT_STREAMS: 4,
SETTINGS_CURRENT_CWND: 5,
SETTINGS_DOWNLOAD_RETRANS_RATE: 6,
SETTINGS_INITIAL_WINDOW_SIZE: 7,
SETTINGS_CLIENT_CERTIFICATE_VECTOR_SIZE: 8
}
exports.settingsIndex = [
null,
'upload_bandwidth',
'download_bandwidth',
'round_trip_time',
'max_concurrent_streams',
'current_cwnd',
'download_retrans_rate',
'initial_window_size',
'client_certificate_vector_size'
]
exports.DEFAULT_WINDOW = 64 * 1024
exports.MAX_INITIAL_WINDOW_SIZE = 2147483647
exports.goaway = {
OK: 0,
PROTOCOL_ERROR: 1,
INTERNAL_ERROR: 2
}
exports.goawayByCode = base.utils.reverse(exports.goaway)
exports.statusReason = {
100: 'Continue',
101: 'Switching Protocols',
102: 'Processing', // RFC 2518, obsoleted by RFC 4918
200: 'OK',
201: 'Created',
202: 'Accepted',
203: 'Non-Authoritative Information',
204: 'No Content',
205: 'Reset Content',
206: 'Partial Content',
207: 'Multi-Status', // RFC 4918
300: 'Multiple Choices',
301: 'Moved Permanently',
302: 'Moved Temporarily',
303: 'See Other',
304: 'Not Modified',
305: 'Use Proxy',
307: 'Temporary Redirect',
308: 'Permanent Redirect', // RFC 7238
400: 'Bad Request',
401: 'Unauthorized',
402: 'Payment Required',
403: 'Forbidden',
404: 'Not Found',
405: 'Method Not Allowed',
406: 'Not Acceptable',
407: 'Proxy Authentication Required',
408: 'Request Time-out',
409: 'Conflict',
410: 'Gone',
411: 'Length Required',
412: 'Precondition Failed',
413: 'Request Entity Too Large',
414: 'Request-URI Too Large',
415: 'Unsupported Media Type',
416: 'Requested Range Not Satisfiable',
417: 'Expectation Failed',
418: 'I\'m a teapot', // RFC 2324
422: 'Unprocessable Entity', // RFC 4918
423: 'Locked', // RFC 4918
424: 'Failed Dependency', // RFC 4918
425: 'Unordered Collection', // RFC 4918
426: 'Upgrade Required', // RFC 2817
428: 'Precondition Required', // RFC 6585
429: 'Too Many Requests', // RFC 6585
431: 'Request Header Fields Too Large', // RFC 6585
500: 'Internal Server Error',
501: 'Not Implemented',
502: 'Bad Gateway',
503: 'Service Unavailable',
504: 'Gateway Time-out',
505: 'HTTP Version Not Supported',
506: 'Variant Also Negotiates', // RFC 2295
507: 'Insufficient Storage', // RFC 4918
509: 'Bandwidth Limit Exceeded',
510: 'Not Extended', // RFC 2774
511: 'Network Authentication Required' // RFC 6585
}

View file

@ -0,0 +1,203 @@
'use strict'
var dictionary = {}
module.exports = dictionary
dictionary[2] = Buffer.from([
'optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-',
'languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi',
'f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser',
'-agent10010120020120220320420520630030130230330430530630740040140240340440',
'5406407408409410411412413414415416417500501502503504505accept-rangesageeta',
'glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic',
'ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran',
'sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati',
'oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo',
'ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe',
'pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic',
'ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1',
'.1statusversionurl\x00'
].join(''))
dictionary[3] = Buffer.from([
0x00, 0x00, 0x00, 0x07, 0x6f, 0x70, 0x74, 0x69, // ....opti
0x6f, 0x6e, 0x73, 0x00, 0x00, 0x00, 0x04, 0x68, // ons....h
0x65, 0x61, 0x64, 0x00, 0x00, 0x00, 0x04, 0x70, // ead....p
0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x03, 0x70, // ost....p
0x75, 0x74, 0x00, 0x00, 0x00, 0x06, 0x64, 0x65, // ut....de
0x6c, 0x65, 0x74, 0x65, 0x00, 0x00, 0x00, 0x05, // lete....
0x74, 0x72, 0x61, 0x63, 0x65, 0x00, 0x00, 0x00, // trace...
0x06, 0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x00, // .accept.
0x00, 0x00, 0x0e, 0x61, 0x63, 0x63, 0x65, 0x70, // ...accep
0x74, 0x2d, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // t-charse
0x74, 0x00, 0x00, 0x00, 0x0f, 0x61, 0x63, 0x63, // t....acc
0x65, 0x70, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // ept-enco
0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x0f, // ding....
0x61, 0x63, 0x63, 0x65, 0x70, 0x74, 0x2d, 0x6c, // accept-l
0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, 0x00, // anguage.
0x00, 0x00, 0x0d, 0x61, 0x63, 0x63, 0x65, 0x70, // ...accep
0x74, 0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x73, // t-ranges
0x00, 0x00, 0x00, 0x03, 0x61, 0x67, 0x65, 0x00, // ....age.
0x00, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x77, // ...allow
0x00, 0x00, 0x00, 0x0d, 0x61, 0x75, 0x74, 0x68, // ....auth
0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, // orizatio
0x6e, 0x00, 0x00, 0x00, 0x0d, 0x63, 0x61, 0x63, // n....cac
0x68, 0x65, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x72, // he-contr
0x6f, 0x6c, 0x00, 0x00, 0x00, 0x0a, 0x63, 0x6f, // ol....co
0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, // nnection
0x00, 0x00, 0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, // ....cont
0x65, 0x6e, 0x74, 0x2d, 0x62, 0x61, 0x73, 0x65, // ent-base
0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, 0x6e, 0x74, // ....cont
0x65, 0x6e, 0x74, 0x2d, 0x65, 0x6e, 0x63, 0x6f, // ent-enco
0x64, 0x69, 0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, // ding....
0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, // content-
0x6c, 0x61, 0x6e, 0x67, 0x75, 0x61, 0x67, 0x65, // language
0x00, 0x00, 0x00, 0x0e, 0x63, 0x6f, 0x6e, 0x74, // ....cont
0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x65, 0x6e, 0x67, // ent-leng
0x74, 0x68, 0x00, 0x00, 0x00, 0x10, 0x63, 0x6f, // th....co
0x6e, 0x74, 0x65, 0x6e, 0x74, 0x2d, 0x6c, 0x6f, // ntent-lo
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, // cation..
0x00, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // ..conten
0x74, 0x2d, 0x6d, 0x64, 0x35, 0x00, 0x00, 0x00, // t-md5...
0x0d, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, // .content
0x2d, 0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, // -range..
0x00, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, // ..conten
0x74, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x00, 0x00, // t-type..
0x00, 0x04, 0x64, 0x61, 0x74, 0x65, 0x00, 0x00, // ..date..
0x00, 0x04, 0x65, 0x74, 0x61, 0x67, 0x00, 0x00, // ..etag..
0x00, 0x06, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, // ..expect
0x00, 0x00, 0x00, 0x07, 0x65, 0x78, 0x70, 0x69, // ....expi
0x72, 0x65, 0x73, 0x00, 0x00, 0x00, 0x04, 0x66, // res....f
0x72, 0x6f, 0x6d, 0x00, 0x00, 0x00, 0x04, 0x68, // rom....h
0x6f, 0x73, 0x74, 0x00, 0x00, 0x00, 0x08, 0x69, // ost....i
0x66, 0x2d, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, // f-match.
0x00, 0x00, 0x11, 0x69, 0x66, 0x2d, 0x6d, 0x6f, // ...if-mo
0x64, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2d, 0x73, // dified-s
0x69, 0x6e, 0x63, 0x65, 0x00, 0x00, 0x00, 0x0d, // ince....
0x69, 0x66, 0x2d, 0x6e, 0x6f, 0x6e, 0x65, 0x2d, // if-none-
0x6d, 0x61, 0x74, 0x63, 0x68, 0x00, 0x00, 0x00, // match...
0x08, 0x69, 0x66, 0x2d, 0x72, 0x61, 0x6e, 0x67, // .if-rang
0x65, 0x00, 0x00, 0x00, 0x13, 0x69, 0x66, 0x2d, // e....if-
0x75, 0x6e, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, // unmodifi
0x65, 0x64, 0x2d, 0x73, 0x69, 0x6e, 0x63, 0x65, // ed-since
0x00, 0x00, 0x00, 0x0d, 0x6c, 0x61, 0x73, 0x74, // ....last
0x2d, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x69, 0x65, // -modifie
0x64, 0x00, 0x00, 0x00, 0x08, 0x6c, 0x6f, 0x63, // d....loc
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, // ation...
0x0c, 0x6d, 0x61, 0x78, 0x2d, 0x66, 0x6f, 0x72, // .max-for
0x77, 0x61, 0x72, 0x64, 0x73, 0x00, 0x00, 0x00, // wards...
0x06, 0x70, 0x72, 0x61, 0x67, 0x6d, 0x61, 0x00, // .pragma.
0x00, 0x00, 0x12, 0x70, 0x72, 0x6f, 0x78, 0x79, // ...proxy
0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, // -authent
0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, 0x00, // icate...
0x13, 0x70, 0x72, 0x6f, 0x78, 0x79, 0x2d, 0x61, // .proxy-a
0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, // uthoriza
0x74, 0x69, 0x6f, 0x6e, 0x00, 0x00, 0x00, 0x05, // tion....
0x72, 0x61, 0x6e, 0x67, 0x65, 0x00, 0x00, 0x00, // range...
0x07, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x72, // .referer
0x00, 0x00, 0x00, 0x0b, 0x72, 0x65, 0x74, 0x72, // ....retr
0x79, 0x2d, 0x61, 0x66, 0x74, 0x65, 0x72, 0x00, // y-after.
0x00, 0x00, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, // ...serve
0x72, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x00, // r....te.
0x00, 0x00, 0x07, 0x74, 0x72, 0x61, 0x69, 0x6c, // ...trail
0x65, 0x72, 0x00, 0x00, 0x00, 0x11, 0x74, 0x72, // er....tr
0x61, 0x6e, 0x73, 0x66, 0x65, 0x72, 0x2d, 0x65, // ansfer-e
0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x00, // ncoding.
0x00, 0x00, 0x07, 0x75, 0x70, 0x67, 0x72, 0x61, // ...upgra
0x64, 0x65, 0x00, 0x00, 0x00, 0x0a, 0x75, 0x73, // de....us
0x65, 0x72, 0x2d, 0x61, 0x67, 0x65, 0x6e, 0x74, // er-agent
0x00, 0x00, 0x00, 0x04, 0x76, 0x61, 0x72, 0x79, // ....vary
0x00, 0x00, 0x00, 0x03, 0x76, 0x69, 0x61, 0x00, // ....via.
0x00, 0x00, 0x07, 0x77, 0x61, 0x72, 0x6e, 0x69, // ...warni
0x6e, 0x67, 0x00, 0x00, 0x00, 0x10, 0x77, 0x77, // ng....ww
0x77, 0x2d, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, // w-authen
0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x00, 0x00, // ticate..
0x00, 0x06, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, // ..method
0x00, 0x00, 0x00, 0x03, 0x67, 0x65, 0x74, 0x00, // ....get.
0x00, 0x00, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, // ...statu
0x73, 0x00, 0x00, 0x00, 0x06, 0x32, 0x30, 0x30, // s....200
0x20, 0x4f, 0x4b, 0x00, 0x00, 0x00, 0x07, 0x76, // .OK....v
0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x00, 0x00, // ersion..
0x00, 0x08, 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, // ..HTTP.1
0x2e, 0x31, 0x00, 0x00, 0x00, 0x03, 0x75, 0x72, // .1....ur
0x6c, 0x00, 0x00, 0x00, 0x06, 0x70, 0x75, 0x62, // l....pub
0x6c, 0x69, 0x63, 0x00, 0x00, 0x00, 0x0a, 0x73, // lic....s
0x65, 0x74, 0x2d, 0x63, 0x6f, 0x6f, 0x6b, 0x69, // et-cooki
0x65, 0x00, 0x00, 0x00, 0x0a, 0x6b, 0x65, 0x65, // e....kee
0x70, 0x2d, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x00, // p-alive.
0x00, 0x00, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, // ...origi
0x6e, 0x31, 0x30, 0x30, 0x31, 0x30, 0x31, 0x32, // n1001012
0x30, 0x31, 0x32, 0x30, 0x32, 0x32, 0x30, 0x35, // 01202205
0x32, 0x30, 0x36, 0x33, 0x30, 0x30, 0x33, 0x30, // 20630030
0x32, 0x33, 0x30, 0x33, 0x33, 0x30, 0x34, 0x33, // 23033043
0x30, 0x35, 0x33, 0x30, 0x36, 0x33, 0x30, 0x37, // 05306307
0x34, 0x30, 0x32, 0x34, 0x30, 0x35, 0x34, 0x30, // 40240540
0x36, 0x34, 0x30, 0x37, 0x34, 0x30, 0x38, 0x34, // 64074084
0x30, 0x39, 0x34, 0x31, 0x30, 0x34, 0x31, 0x31, // 09410411
0x34, 0x31, 0x32, 0x34, 0x31, 0x33, 0x34, 0x31, // 41241341
0x34, 0x34, 0x31, 0x35, 0x34, 0x31, 0x36, 0x34, // 44154164
0x31, 0x37, 0x35, 0x30, 0x32, 0x35, 0x30, 0x34, // 17502504
0x35, 0x30, 0x35, 0x32, 0x30, 0x33, 0x20, 0x4e, // 505203.N
0x6f, 0x6e, 0x2d, 0x41, 0x75, 0x74, 0x68, 0x6f, // on-Autho
0x72, 0x69, 0x74, 0x61, 0x74, 0x69, 0x76, 0x65, // ritative
0x20, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, // .Informa
0x74, 0x69, 0x6f, 0x6e, 0x32, 0x30, 0x34, 0x20, // tion204.
0x4e, 0x6f, 0x20, 0x43, 0x6f, 0x6e, 0x74, 0x65, // No.Conte
0x6e, 0x74, 0x33, 0x30, 0x31, 0x20, 0x4d, 0x6f, // nt301.Mo
0x76, 0x65, 0x64, 0x20, 0x50, 0x65, 0x72, 0x6d, // ved.Perm
0x61, 0x6e, 0x65, 0x6e, 0x74, 0x6c, 0x79, 0x34, // anently4
0x30, 0x30, 0x20, 0x42, 0x61, 0x64, 0x20, 0x52, // 00.Bad.R
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x34, 0x30, // equest40
0x31, 0x20, 0x55, 0x6e, 0x61, 0x75, 0x74, 0x68, // 1.Unauth
0x6f, 0x72, 0x69, 0x7a, 0x65, 0x64, 0x34, 0x30, // orized40
0x33, 0x20, 0x46, 0x6f, 0x72, 0x62, 0x69, 0x64, // 3.Forbid
0x64, 0x65, 0x6e, 0x34, 0x30, 0x34, 0x20, 0x4e, // den404.N
0x6f, 0x74, 0x20, 0x46, 0x6f, 0x75, 0x6e, 0x64, // ot.Found
0x35, 0x30, 0x30, 0x20, 0x49, 0x6e, 0x74, 0x65, // 500.Inte
0x72, 0x6e, 0x61, 0x6c, 0x20, 0x53, 0x65, 0x72, // rnal.Ser
0x76, 0x65, 0x72, 0x20, 0x45, 0x72, 0x72, 0x6f, // ver.Erro
0x72, 0x35, 0x30, 0x31, 0x20, 0x4e, 0x6f, 0x74, // r501.Not
0x20, 0x49, 0x6d, 0x70, 0x6c, 0x65, 0x6d, 0x65, // .Impleme
0x6e, 0x74, 0x65, 0x64, 0x35, 0x30, 0x33, 0x20, // nted503.
0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x20, // Service.
0x55, 0x6e, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, // Unavaila
0x62, 0x6c, 0x65, 0x4a, 0x61, 0x6e, 0x20, 0x46, // bleJan.F
0x65, 0x62, 0x20, 0x4d, 0x61, 0x72, 0x20, 0x41, // eb.Mar.A
0x70, 0x72, 0x20, 0x4d, 0x61, 0x79, 0x20, 0x4a, // pr.May.J
0x75, 0x6e, 0x20, 0x4a, 0x75, 0x6c, 0x20, 0x41, // un.Jul.A
0x75, 0x67, 0x20, 0x53, 0x65, 0x70, 0x74, 0x20, // ug.Sept.
0x4f, 0x63, 0x74, 0x20, 0x4e, 0x6f, 0x76, 0x20, // Oct.Nov.
0x44, 0x65, 0x63, 0x20, 0x30, 0x30, 0x3a, 0x30, // Dec.00.0
0x30, 0x3a, 0x30, 0x30, 0x20, 0x4d, 0x6f, 0x6e, // 0.00.Mon
0x2c, 0x20, 0x54, 0x75, 0x65, 0x2c, 0x20, 0x57, // ..Tue..W
0x65, 0x64, 0x2c, 0x20, 0x54, 0x68, 0x75, 0x2c, // ed..Thu.
0x20, 0x46, 0x72, 0x69, 0x2c, 0x20, 0x53, 0x61, // .Fri..Sa
0x74, 0x2c, 0x20, 0x53, 0x75, 0x6e, 0x2c, 0x20, // t..Sun..
0x47, 0x4d, 0x54, 0x63, 0x68, 0x75, 0x6e, 0x6b, // GMTchunk
0x65, 0x64, 0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, // ed.text.
0x68, 0x74, 0x6d, 0x6c, 0x2c, 0x69, 0x6d, 0x61, // html.ima
0x67, 0x65, 0x2f, 0x70, 0x6e, 0x67, 0x2c, 0x69, // ge.png.i
0x6d, 0x61, 0x67, 0x65, 0x2f, 0x6a, 0x70, 0x67, // mage.jpg
0x2c, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2f, 0x67, // .image.g
0x69, 0x66, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // if.appli
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // cation.x
0x6d, 0x6c, 0x2c, 0x61, 0x70, 0x70, 0x6c, 0x69, // ml.appli
0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x78, // cation.x
0x68, 0x74, 0x6d, 0x6c, 0x2b, 0x78, 0x6d, 0x6c, // html.xml
0x2c, 0x74, 0x65, 0x78, 0x74, 0x2f, 0x70, 0x6c, // .text.pl
0x61, 0x69, 0x6e, 0x2c, 0x74, 0x65, 0x78, 0x74, // ain.text
0x2f, 0x6a, 0x61, 0x76, 0x61, 0x73, 0x63, 0x72, // .javascr
0x69, 0x70, 0x74, 0x2c, 0x70, 0x75, 0x62, 0x6c, // ipt.publ
0x69, 0x63, 0x70, 0x72, 0x69, 0x76, 0x61, 0x74, // icprivat
0x65, 0x6d, 0x61, 0x78, 0x2d, 0x61, 0x67, 0x65, // emax-age
0x3d, 0x67, 0x7a, 0x69, 0x70, 0x2c, 0x64, 0x65, // .gzip.de
0x66, 0x6c, 0x61, 0x74, 0x65, 0x2c, 0x73, 0x64, // flate.sd
0x63, 0x68, 0x63, 0x68, 0x61, 0x72, 0x73, 0x65, // chcharse
0x74, 0x3d, 0x75, 0x74, 0x66, 0x2d, 0x38, 0x63, // t.utf-8c
0x68, 0x61, 0x72, 0x73, 0x65, 0x74, 0x3d, 0x69, // harset.i
0x73, 0x6f, 0x2d, 0x38, 0x38, 0x35, 0x39, 0x2d, // so-8859-
0x31, 0x2c, 0x75, 0x74, 0x66, 0x2d, 0x2c, 0x2a, // 1.utf-..
0x2c, 0x65, 0x6e, 0x71, 0x3d, 0x30, 0x2e // .enq.0.
])
dictionary[3.1] = dictionary[3]

View file

@ -0,0 +1,519 @@
'use strict'
var transport = require('../../../spdy-transport')
var constants = require('./').constants
var base = transport.protocol.base
var utils = base.utils
var assert = require('assert')
var util = require('util')
var Buffer = require('buffer').Buffer
var WriteBuffer = require('wbuf')
var debug = require('debug')('spdy:framer')
function Framer (options) {
base.Framer.call(this, options)
}
util.inherits(Framer, base.Framer)
module.exports = Framer
Framer.create = function create (options) {
return new Framer(options)
}
Framer.prototype.setMaxFrameSize = function setMaxFrameSize (size) {
// http2-only
}
Framer.prototype.headersToDict = function headersToDict (headers,
preprocess,
callback) {
function stringify (value) {
if (value !== undefined) {
if (Array.isArray(value)) {
return value.join('\x00')
} else if (typeof value === 'string') {
return value
} else {
return value.toString()
}
} else {
return ''
}
}
// Lower case of all headers keys
var loweredHeaders = {}
Object.keys(headers || {}).map(function (key) {
loweredHeaders[key.toLowerCase()] = headers[key]
})
// Allow outer code to add custom headers or remove something
if (preprocess) { preprocess(loweredHeaders) }
// Transform object into kv pairs
var size = this.version === 2 ? 2 : 4
var len = size
var pairs = Object.keys(loweredHeaders).filter(function (key) {
var lkey = key.toLowerCase()
// Will be in `:host`
if (lkey === 'host' && this.version >= 3) {
return false
}
return lkey !== 'connection' && lkey !== 'keep-alive' &&
lkey !== 'proxy-connection' && lkey !== 'transfer-encoding'
}, this).map(function (key) {
var klen = Buffer.byteLength(key)
var value = stringify(loweredHeaders[key])
var vlen = Buffer.byteLength(value)
len += size * 2 + klen + vlen
return [klen, key, vlen, value]
})
var block = new WriteBuffer()
block.reserve(len)
if (this.version === 2) {
block.writeUInt16BE(pairs.length)
} else {
block.writeUInt32BE(pairs.length)
}
pairs.forEach(function (pair) {
// Write key length
if (this.version === 2) {
block.writeUInt16BE(pair[0])
} else {
block.writeUInt32BE(pair[0])
}
// Write key
block.write(pair[1])
// Write value length
if (this.version === 2) {
block.writeUInt16BE(pair[2])
} else {
block.writeUInt32BE(pair[2])
}
// Write value
block.write(pair[3])
}, this)
assert(this.compress !== null, 'Framer version not initialized')
this.compress.write(block.render(), callback)
}
Framer.prototype._frame = function _frame (frame, body, callback) {
if (!this.version) {
this.on('version', function () {
this._frame(frame, body, callback)
})
return
}
debug('id=%d type=%s', frame.id, frame.type)
var buffer = new WriteBuffer()
buffer.writeUInt16BE(0x8000 | this.version)
buffer.writeUInt16BE(constants.frameType[frame.type])
buffer.writeUInt8(frame.flags)
var len = buffer.skip(3)
body(buffer)
var frameSize = buffer.size - constants.FRAME_HEADER_SIZE
len.writeUInt24BE(frameSize)
var chunks = buffer.render()
var toWrite = {
stream: frame.id,
priority: false,
chunks: chunks,
callback: callback
}
this._resetTimeout()
this.schedule(toWrite)
return chunks
}
Framer.prototype._synFrame = function _synFrame (frame, callback) {
var self = this
if (!frame.path) {
throw new Error('`path` is required frame argument')
}
function preprocess (headers) {
var method = frame.method || base.constants.DEFAULT_METHOD
var version = frame.version || 'HTTP/1.1'
var scheme = frame.scheme || 'https'
var host = frame.host ||
(frame.headers && frame.headers.host) ||
base.constants.DEFAULT_HOST
if (self.version === 2) {
headers.method = method
headers.version = version
headers.url = frame.path
headers.scheme = scheme
headers.host = host
if (frame.status) {
headers.status = frame.status
}
} else {
headers[':method'] = method
headers[':version'] = version
headers[':path'] = frame.path
headers[':scheme'] = scheme
headers[':host'] = host
if (frame.status) { headers[':status'] = frame.status }
}
}
this.headersToDict(frame.headers, preprocess, function (err, chunks) {
if (err) {
if (callback) {
return callback(err)
} else {
return self.emit('error', err)
}
}
self._frame({
type: 'SYN_STREAM',
id: frame.id,
flags: frame.fin ? constants.flags.FLAG_FIN : 0
}, function (buf) {
buf.reserve(10)
buf.writeUInt32BE(frame.id & 0x7fffffff)
buf.writeUInt32BE(frame.associated & 0x7fffffff)
var weight = (frame.priority && frame.priority.weight) ||
constants.DEFAULT_WEIGHT
// We only have 3 bits for priority in SPDY, try to fit it into this
var priority = utils.weightToPriority(weight)
buf.writeUInt8(priority << 5)
// CREDENTIALS slot
buf.writeUInt8(0)
for (var i = 0; i < chunks.length; i++) {
buf.copyFrom(chunks[i])
}
}, callback)
})
}
Framer.prototype.requestFrame = function requestFrame (frame, callback) {
this._synFrame({
id: frame.id,
fin: frame.fin,
associated: 0,
method: frame.method,
version: frame.version,
scheme: frame.scheme,
host: frame.host,
path: frame.path,
priority: frame.priority,
headers: frame.headers
}, callback)
}
Framer.prototype.responseFrame = function responseFrame (frame, callback) {
var self = this
var reason = frame.reason
if (!reason) {
reason = constants.statusReason[frame.status]
}
function preprocess (headers) {
if (self.version === 2) {
headers.status = frame.status + ' ' + reason
headers.version = 'HTTP/1.1'
} else {
headers[':status'] = frame.status + ' ' + reason
headers[':version'] = 'HTTP/1.1'
}
}
this.headersToDict(frame.headers, preprocess, function (err, chunks) {
if (err) {
if (callback) {
return callback(err)
} else {
return self.emit('error', err)
}
}
self._frame({
type: 'SYN_REPLY',
id: frame.id,
flags: 0
}, function (buf) {
buf.reserve(self.version === 2 ? 6 : 4)
buf.writeUInt32BE(frame.id & 0x7fffffff)
// Unused data
if (self.version === 2) {
buf.writeUInt16BE(0)
}
for (var i = 0; i < chunks.length; i++) {
buf.copyFrom(chunks[i])
}
}, callback)
})
}
Framer.prototype.pushFrame = function pushFrame (frame, callback) {
var self = this
this._checkPush(function (err) {
if (err) { return callback(err) }
self._synFrame({
id: frame.promisedId,
associated: frame.id,
method: frame.method,
status: frame.status || 200,
version: frame.version,
scheme: frame.scheme,
host: frame.host,
path: frame.path,
priority: frame.priority,
// Merge everything together, there is no difference in SPDY protocol
headers: Object.assign(Object.assign({}, frame.headers), frame.response)
}, callback)
})
}
Framer.prototype.headersFrame = function headersFrame (frame, callback) {
var self = this
this.headersToDict(frame.headers, null, function (err, chunks) {
if (err) {
if (callback) { return callback(err) } else {
return self.emit('error', err)
}
}
self._frame({
type: 'HEADERS',
id: frame.id,
priority: false,
flags: 0
}, function (buf) {
buf.reserve(4 + (self.version === 2 ? 2 : 0))
buf.writeUInt32BE(frame.id & 0x7fffffff)
// Unused data
if (self.version === 2) { buf.writeUInt16BE(0) }
for (var i = 0; i < chunks.length; i++) {
buf.copyFrom(chunks[i])
}
}, callback)
})
}
Framer.prototype.dataFrame = function dataFrame (frame, callback) {
if (!this.version) {
return this.on('version', function () {
this.dataFrame(frame, callback)
})
}
debug('id=%d type=DATA', frame.id)
var buffer = new WriteBuffer()
buffer.reserve(8 + frame.data.length)
buffer.writeUInt32BE(frame.id & 0x7fffffff)
buffer.writeUInt8(frame.fin ? 0x01 : 0x0)
buffer.writeUInt24BE(frame.data.length)
buffer.copyFrom(frame.data)
var chunks = buffer.render()
var toWrite = {
stream: frame.id,
priority: frame.priority,
chunks: chunks,
callback: callback
}
var self = this
this._resetTimeout()
var bypass = this.version < 3.1
this.window.send.update(-frame.data.length, bypass ? undefined : function () {
self._resetTimeout()
self.schedule(toWrite)
})
if (bypass) {
this._resetTimeout()
this.schedule(toWrite)
}
}
Framer.prototype.pingFrame = function pingFrame (frame, callback) {
this._frame({
type: 'PING',
id: 0,
flags: 0
}, function (buf, callback) {
buf.reserve(4)
var opaque = frame.opaque
buf.writeUInt32BE(opaque.readUInt32BE(opaque.length - 4, true))
}, callback)
}
Framer.prototype.rstFrame = function rstFrame (frame, callback) {
this._frame({
type: 'RST_STREAM',
id: frame.id,
flags: 0
}, function (buf) {
buf.reserve(8)
// Stream ID
buf.writeUInt32BE(frame.id & 0x7fffffff)
// Status Code
buf.writeUInt32BE(constants.error[frame.code])
// Extra debugging information
if (frame.extra) {
buf.write(frame.extra)
}
}, callback)
}
Framer.prototype.prefaceFrame = function prefaceFrame () {
}
Framer.prototype.settingsFrame = function settingsFrame (options, callback) {
var self = this
var key = this.version + '/' + JSON.stringify(options)
var settings = Framer.settingsCache[key]
if (settings) {
debug('cached settings')
this._resetTimeout()
this.schedule({
stream: 0,
priority: false,
chunks: settings,
callback: callback
})
return
}
var params = []
for (var i = 0; i < constants.settingsIndex.length; i++) {
var name = constants.settingsIndex[i]
if (!name) { continue }
// value: Infinity
if (!isFinite(options[name])) {
continue
}
if (options[name] !== undefined) {
params.push({ key: i, value: options[name] })
}
}
var frame = this._frame({
type: 'SETTINGS',
id: 0,
flags: 0
}, function (buf) {
buf.reserve(4 + 8 * params.length)
// Count of entries
buf.writeUInt32BE(params.length)
params.forEach(function (param) {
var flag = constants.settings.FLAG_SETTINGS_PERSIST_VALUE << 24
if (self.version === 2) {
buf.writeUInt32LE(flag | param.key)
} else { buf.writeUInt32BE(flag | param.key) }
buf.writeUInt32BE(param.value & 0x7fffffff)
})
}, callback)
Framer.settingsCache[key] = frame
}
Framer.settingsCache = {}
Framer.prototype.ackSettingsFrame = function ackSettingsFrame (callback) {
if (callback) {
process.nextTick(callback)
}
}
Framer.prototype.windowUpdateFrame = function windowUpdateFrame (frame,
callback) {
this._frame({
type: 'WINDOW_UPDATE',
id: frame.id,
flags: 0
}, function (buf) {
buf.reserve(8)
// ID
buf.writeUInt32BE(frame.id & 0x7fffffff)
// Delta
buf.writeInt32BE(frame.delta)
}, callback)
}
Framer.prototype.goawayFrame = function goawayFrame (frame, callback) {
this._frame({
type: 'GOAWAY',
id: 0,
flags: 0
}, function (buf) {
buf.reserve(8)
// Last-good-stream-ID
buf.writeUInt32BE(frame.lastId & 0x7fffffff)
// Status
buf.writeUInt32BE(constants.goaway[frame.code])
}, callback)
}
Framer.prototype.priorityFrame = function priorityFrame (frame, callback) {
// No such thing in SPDY
if (callback) {
process.nextTick(callback)
}
}
Framer.prototype.xForwardedFor = function xForwardedFor (frame, callback) {
this._frame({
type: 'X_FORWARDED_FOR',
id: 0,
flags: 0
}, function (buf) {
buf.writeUInt32BE(Buffer.byteLength(frame.host))
buf.write(frame.host)
}, callback)
}

View file

@ -0,0 +1,9 @@
'use strict'
exports.name = 'spdy'
exports.dictionary = require('./dictionary')
exports.constants = require('./constants')
exports.parser = require('./parser')
exports.framer = require('./framer')
exports.compressionPool = require('./zlib-pool')

View file

@ -0,0 +1,485 @@
'use strict'
var parser = exports
var transport = require('../../../spdy-transport')
var base = transport.protocol.base
var utils = base.utils
var constants = require('./constants')
var assert = require('assert')
var util = require('util')
var OffsetBuffer = require('obuf')
function Parser (options) {
base.Parser.call(this, options)
this.isServer = options.isServer
this.waiting = constants.FRAME_HEADER_SIZE
this.state = 'frame-head'
this.pendingHeader = null
}
util.inherits(Parser, base.Parser)
parser.create = function create (options) {
return new Parser(options)
}
Parser.prototype.setMaxFrameSize = function setMaxFrameSize (size) {
// http2-only
}
Parser.prototype.setMaxHeaderListSize = function setMaxHeaderListSize (size) {
// http2-only
}
// Only for testing
Parser.prototype.skipPreface = function skipPreface () {
}
Parser.prototype.execute = function execute (buffer, callback) {
if (this.state === 'frame-head') { return this.onFrameHead(buffer, callback) }
assert(this.state === 'frame-body' && this.pendingHeader !== null)
var self = this
var header = this.pendingHeader
this.pendingHeader = null
this.onFrameBody(header, buffer, function (err, frame) {
if (err) {
return callback(err)
}
self.state = 'frame-head'
self.waiting = constants.FRAME_HEADER_SIZE
self.partial = false
callback(null, frame)
})
}
Parser.prototype.executePartial = function executePartial (buffer, callback) {
var header = this.pendingHeader
if (this.window) {
this.window.recv.update(-buffer.size)
}
// DATA frame
callback(null, {
type: 'DATA',
id: header.id,
// Partial DATA can't be FIN
fin: false,
data: buffer.take(buffer.size)
})
}
Parser.prototype.onFrameHead = function onFrameHead (buffer, callback) {
var header = {
control: (buffer.peekUInt8() & 0x80) === 0x80,
version: null,
type: null,
id: null,
flags: null,
length: null
}
if (header.control) {
header.version = buffer.readUInt16BE() & 0x7fff
header.type = buffer.readUInt16BE()
} else {
header.id = buffer.readUInt32BE(0) & 0x7fffffff
}
header.flags = buffer.readUInt8()
header.length = buffer.readUInt24BE()
if (this.version === null && header.control) {
// TODO(indutny): do ProtocolError here and in the rest of errors
if (header.version !== 2 && header.version !== 3) {
return callback(new Error('Unsupported SPDY version: ' + header.version))
}
this.setVersion(header.version)
}
this.state = 'frame-body'
this.waiting = header.length
this.pendingHeader = header
this.partial = !header.control
callback(null, null)
}
Parser.prototype.onFrameBody = function onFrameBody (header, buffer, callback) {
// Data frame
if (!header.control) {
// Count received bytes
if (this.window) {
this.window.recv.update(-buffer.size)
}
// No support for compressed DATA
if ((header.flags & constants.flags.FLAG_COMPRESSED) !== 0) {
return callback(new Error('DATA compression not supported'))
}
if (header.id === 0) {
return callback(this.error(constants.error.PROTOCOL_ERROR,
'Invalid stream id for DATA'))
}
return callback(null, {
type: 'DATA',
id: header.id,
fin: (header.flags & constants.flags.FLAG_FIN) !== 0,
data: buffer.take(buffer.size)
})
}
if (header.type === 0x01 || header.type === 0x02) { // SYN_STREAM or SYN_REPLY
this.onSynHeadFrame(header.type, header.flags, buffer, callback)
} else if (header.type === 0x03) { // RST_STREAM
this.onRSTFrame(buffer, callback)
} else if (header.type === 0x04) { // SETTINGS
this.onSettingsFrame(buffer, callback)
} else if (header.type === 0x05) {
callback(null, { type: 'NOOP' })
} else if (header.type === 0x06) { // PING
this.onPingFrame(buffer, callback)
} else if (header.type === 0x07) { // GOAWAY
this.onGoawayFrame(buffer, callback)
} else if (header.type === 0x08) { // HEADERS
this.onHeaderFrames(buffer, callback)
} else if (header.type === 0x09) { // WINDOW_UPDATE
this.onWindowUpdateFrame(buffer, callback)
} else if (header.type === 0xf000) { // X-FORWARDED
this.onXForwardedFrame(buffer, callback)
} else {
callback(null, { type: 'unknown: ' + header.type })
}
}
Parser.prototype._filterHeader = function _filterHeader (headers, name) {
var res = {}
var keys = Object.keys(headers)
for (var i = 0; i < keys.length; i++) {
var key = keys[i]
if (key !== name) {
res[key] = headers[key]
}
}
return res
}
Parser.prototype.onSynHeadFrame = function onSynHeadFrame (type,
flags,
body,
callback) {
var self = this
var stream = type === 0x01
var offset = stream ? 10 : this.version === 2 ? 6 : 4
if (!body.has(offset)) {
return callback(new Error('SynHead OOB'))
}
var head = body.clone(offset)
body.skip(offset)
this.parseKVs(body, function (err, headers) {
if (err) {
return callback(err)
}
if (stream &&
(!headers[':method'] || !headers[':path'])) {
return callback(new Error('Missing `:method` and/or `:path` header'))
}
var id = head.readUInt32BE() & 0x7fffffff
if (id === 0) {
return callback(self.error(constants.error.PROTOCOL_ERROR,
'Invalid stream id for HEADERS'))
}
var associated = stream ? head.readUInt32BE() & 0x7fffffff : 0
var priority = stream
? head.readUInt8() >> 5
: utils.weightToPriority(constants.DEFAULT_WEIGHT)
var fin = (flags & constants.flags.FLAG_FIN) !== 0
var unidir = (flags & constants.flags.FLAG_UNIDIRECTIONAL) !== 0
var path = headers[':path']
var isPush = stream && associated !== 0
var weight = utils.priorityToWeight(priority)
var priorityInfo = {
weight: weight,
exclusive: false,
parent: 0
}
if (!isPush) {
callback(null, {
type: 'HEADERS',
id: id,
priority: priorityInfo,
fin: fin,
writable: !unidir,
headers: headers,
path: path
})
return
}
if (stream && !headers[':status']) {
return callback(new Error('Missing `:status` header'))
}
var filteredHeaders = self._filterHeader(headers, ':status')
callback(null, [ {
type: 'PUSH_PROMISE',
id: associated,
fin: false,
promisedId: id,
headers: filteredHeaders,
path: path
}, {
type: 'HEADERS',
id: id,
fin: fin,
priority: priorityInfo,
writable: true,
path: undefined,
headers: {
':status': headers[':status']
}
}])
})
}
Parser.prototype.onHeaderFrames = function onHeaderFrames (body, callback) {
var offset = this.version === 2 ? 6 : 4
if (!body.has(offset)) {
return callback(new Error('HEADERS OOB'))
}
var streamId = body.readUInt32BE() & 0x7fffffff
if (this.version === 2) { body.skip(2) }
this.parseKVs(body, function (err, headers) {
if (err) { return callback(err) }
callback(null, {
type: 'HEADERS',
priority: {
parent: 0,
exclusive: false,
weight: constants.DEFAULT_WEIGHT
},
id: streamId,
fin: false,
writable: true,
path: undefined,
headers: headers
})
})
}
Parser.prototype.parseKVs = function parseKVs (buffer, callback) {
var self = this
this.decompress.write(buffer.toChunks(), function (err, chunks) {
if (err) {
return callback(err)
}
var buffer = new OffsetBuffer()
for (var i = 0; i < chunks.length; i++) {
buffer.push(chunks[i])
}
var size = self.version === 2 ? 2 : 4
if (!buffer.has(size)) { return callback(new Error('KV OOB')) }
var count = self.version === 2
? buffer.readUInt16BE()
: buffer.readUInt32BE()
var headers = {}
function readString () {
if (!buffer.has(size)) { return null }
var len = self.version === 2
? buffer.readUInt16BE()
: buffer.readUInt32BE()
if (!buffer.has(len)) { return null }
var value = buffer.take(len)
return value.toString()
}
while (count > 0) {
var key = readString()
var value = readString()
if (key === null || value === null) {
return callback(new Error('Headers OOB'))
}
if (self.version < 3) {
var isInternal = /^(method|version|url|host|scheme|status)$/.test(key)
if (key === 'url') {
key = 'path'
}
if (isInternal) {
key = ':' + key
}
}
// Compatibility with HTTP2
if (key === ':status') {
value = value.split(/ /g, 2)[0]
}
count--
if (key === ':host') {
key = ':authority'
}
// Skip version, not present in HTTP2
if (key === ':version') {
continue
}
value = value.split(/\0/g)
for (var j = 0; j < value.length; j++) {
utils.addHeaderLine(key, value[j], headers)
}
}
callback(null, headers)
})
}
Parser.prototype.onRSTFrame = function onRSTFrame (body, callback) {
if (!body.has(8)) { return callback(new Error('RST OOB')) }
var frame = {
type: 'RST',
id: body.readUInt32BE() & 0x7fffffff,
code: constants.errorByCode[body.readUInt32BE()]
}
if (frame.id === 0) {
return callback(this.error(constants.error.PROTOCOL_ERROR,
'Invalid stream id for RST'))
}
if (body.size !== 0) {
frame.extra = body.take(body.size)
}
callback(null, frame)
}
Parser.prototype.onSettingsFrame = function onSettingsFrame (body, callback) {
if (!body.has(4)) {
return callback(new Error('SETTINGS OOB'))
}
var settings = {}
var number = body.readUInt32BE()
var idMap = {
1: 'upload_bandwidth',
2: 'download_bandwidth',
3: 'round_trip_time',
4: 'max_concurrent_streams',
5: 'current_cwnd',
6: 'download_retrans_rate',
7: 'initial_window_size',
8: 'client_certificate_vector_size'
}
if (!body.has(number * 8)) {
return callback(new Error('SETTINGS OOB#2'))
}
for (var i = 0; i < number; i++) {
var id = this.version === 2
? body.readUInt32LE()
: body.readUInt32BE()
var flags = (id >> 24) & 0xff
id = id & 0xffffff
// Skip persisted settings
if (flags & 0x2) { continue }
var name = idMap[id]
settings[name] = body.readUInt32BE()
}
callback(null, {
type: 'SETTINGS',
settings: settings
})
}
Parser.prototype.onPingFrame = function onPingFrame (body, callback) {
if (!body.has(4)) {
return callback(new Error('PING OOB'))
}
var isServer = this.isServer
var opaque = body.clone(body.size).take(body.size)
var id = body.readUInt32BE()
var ack = isServer ? (id % 2 === 0) : (id % 2 === 1)
callback(null, { type: 'PING', opaque: opaque, ack: ack })
}
Parser.prototype.onGoawayFrame = function onGoawayFrame (body, callback) {
if (!body.has(8)) {
return callback(new Error('GOAWAY OOB'))
}
callback(null, {
type: 'GOAWAY',
lastId: body.readUInt32BE() & 0x7fffffff,
code: constants.goawayByCode[body.readUInt32BE()]
})
}
Parser.prototype.onWindowUpdateFrame = function onWindowUpdateFrame (body,
callback) {
if (!body.has(8)) {
return callback(new Error('WINDOW_UPDATE OOB'))
}
callback(null, {
type: 'WINDOW_UPDATE',
id: body.readUInt32BE() & 0x7fffffff,
delta: body.readInt32BE()
})
}
Parser.prototype.onXForwardedFrame = function onXForwardedFrame (body,
callback) {
if (!body.has(4)) {
return callback(new Error('X_FORWARDED OOB'))
}
var len = body.readUInt32BE()
if (!body.has(len)) { return callback(new Error('X_FORWARDED host length OOB')) }
callback(null, {
type: 'X_FORWARDED_FOR',
host: body.take(len).toString()
})
}

View file

@ -0,0 +1,65 @@
'use strict'
var zlibpool = exports
var zlib = require('zlib')
var transport = require('../../../spdy-transport')
// TODO(indutny): think about it, why has it always been Z_SYNC_FLUSH here.
// It should be possible to manually flush stuff after the write instead
function createDeflate (version, compression) {
var deflate = zlib.createDeflate({
dictionary: transport.protocol.spdy.dictionary[version],
flush: zlib.Z_SYNC_FLUSH,
windowBits: 11,
level: compression ? zlib.Z_DEFAULT_COMPRESSION : zlib.Z_NO_COMPRESSION
})
// For node.js v0.8
deflate._flush = zlib.Z_SYNC_FLUSH
return deflate
}
function createInflate (version) {
var inflate = zlib.createInflate({
dictionary: transport.protocol.spdy.dictionary[version],
flush: zlib.Z_SYNC_FLUSH
})
// For node.js v0.8
inflate._flush = zlib.Z_SYNC_FLUSH
return inflate
}
function Pool (compression) {
this.compression = compression
this.pool = {
2: [],
3: [],
3.1: []
}
}
zlibpool.create = function create (compression) {
return new Pool(compression)
}
Pool.prototype.get = function get (version) {
if (this.pool[version].length > 0) {
return this.pool[version].pop()
} else {
var id = version
return {
version: version,
compress: createDeflate(id, this.compression),
decompress: createInflate(id)
}
}
}
Pool.prototype.put = function put (pair) {
this.pool[pair.version].push(pair)
}

View file

@ -0,0 +1,710 @@
'use strict'
var transport = require('../spdy-transport')
var assert = require('assert')
var util = require('util')
var debug = {
client: require('debug')('spdy:stream:client'),
server: require('debug')('spdy:stream:server')
}
var Duplex = require('readable-stream').Duplex
function Stream (connection, options) {
Duplex.call(this)
var connectionState = connection._spdyState
var state = {}
this._spdyState = state
this.id = options.id
this.method = options.method
this.path = options.path
this.host = options.host
this.headers = options.headers || {}
this.connection = connection
this.parent = options.parent || null
state.socket = null
state.protocol = connectionState.protocol
state.constants = state.protocol.constants
// See _initPriority()
state.priority = null
state.version = this.connection.getVersion()
state.isServer = this.connection.isServer()
state.debug = state.isServer ? debug.server : debug.client
state.framer = connectionState.framer
state.parser = connectionState.parser
state.request = options.request
state.needResponse = options.request
state.window = connectionState.streamWindow.clone(options.id)
state.sessionWindow = connectionState.window
state.maxChunk = connectionState.maxChunk
// Can't send incoming request
// (See `.send()` method)
state.sent = !state.request
state.readable = options.readable !== false
state.writable = options.writable !== false
state.aborted = false
state.corked = 0
state.corkQueue = []
state.timeout = new transport.utils.Timeout(this)
this.on('finish', this._onFinish)
this.on('end', this._onEnd)
var self = this
function _onWindowOverflow () {
self._onWindowOverflow()
}
state.window.recv.on('overflow', _onWindowOverflow)
state.window.send.on('overflow', _onWindowOverflow)
this._initPriority(options.priority)
if (!state.readable) { this.push(null) }
if (!state.writable) {
this._writableState.ended = true
this._writableState.finished = true
}
}
util.inherits(Stream, Duplex)
exports.Stream = Stream
Stream.prototype._init = function _init (socket) {
this.socket = socket
}
Stream.prototype._initPriority = function _initPriority (priority) {
var state = this._spdyState
var connectionState = this.connection._spdyState
var root = connectionState.priorityRoot
if (!priority) {
state.priority = root.addDefault(this.id)
return
}
state.priority = root.add({
id: this.id,
parent: priority.parent,
weight: priority.weight,
exclusive: priority.exclusive
})
}
Stream.prototype._handleFrame = function _handleFrame (frame) {
var state = this._spdyState
// Ignore any kind of data after abort
if (state.aborted) {
state.debug('id=%d ignoring frame=%s after abort', this.id, frame.type)
return
}
// Restart the timer on incoming frames
state.timeout.reset()
if (frame.type === 'DATA') {
this._handleData(frame)
} else if (frame.type === 'HEADERS') {
this._handleHeaders(frame)
} else if (frame.type === 'RST') {
this._handleRST(frame)
} else if (frame.type === 'WINDOW_UPDATE') { this._handleWindowUpdate(frame) } else if (frame.type === 'PRIORITY') {
this._handlePriority(frame)
} else if (frame.type === 'PUSH_PROMISE') { this._handlePushPromise(frame) }
if (frame.fin) {
state.debug('id=%d end', this.id)
this.push(null)
}
}
function checkAborted (stream, state, callback) {
if (state.aborted) {
state.debug('id=%d abort write', stream.id)
process.nextTick(function () {
callback(new Error('Stream write aborted'))
})
return true
}
return false
}
function _send (stream, state, data, callback) {
if (checkAborted(stream, state, callback)) {
return
}
state.debug('id=%d presend=%d', stream.id, data.length)
state.timeout.reset()
state.window.send.update(-data.length, function () {
if (checkAborted(stream, state, callback)) {
return
}
state.debug('id=%d send=%d', stream.id, data.length)
state.timeout.reset()
state.framer.dataFrame({
id: stream.id,
priority: state.priority.getPriority(),
fin: false,
data: data
}, function (err) {
state.debug('id=%d postsend=%d', stream.id, data.length)
callback(err)
})
})
}
Stream.prototype._write = function _write (data, enc, callback) {
var state = this._spdyState
// Send the request if it wasn't sent
if (!state.sent) { this.send() }
// Writes should come after pending control frames (response and headers)
if (state.corked !== 0) {
var self = this
state.corkQueue.push(function () {
self._write(data, enc, callback)
})
return
}
// Split DATA in chunks to prevent window from going negative
this._splitStart(data, _send, callback)
}
Stream.prototype._splitStart = function _splitStart (data, onChunk, callback) {
return this._split(data, 0, onChunk, callback)
}
Stream.prototype._split = function _split (data, offset, onChunk, callback) {
if (offset === data.length) {
return process.nextTick(callback)
}
var state = this._spdyState
var local = state.window.send
var session = state.sessionWindow.send
var availSession = Math.max(0, session.getCurrent())
if (availSession === 0) {
availSession = session.getMax()
}
var availLocal = Math.max(0, local.getCurrent())
if (availLocal === 0) {
availLocal = local.getMax()
}
var avail = Math.min(availSession, availLocal)
avail = Math.min(avail, state.maxChunk)
var self = this
if (avail === 0) {
state.window.send.update(0, function () {
self._split(data, offset, onChunk, callback)
})
return
}
// Split data in chunks in a following way:
var limit = avail
var size = Math.min(data.length - offset, limit)
var chunk = data.slice(offset, offset + size)
onChunk(this, state, chunk, function (err) {
if (err) { return callback(err) }
// Get the next chunk
self._split(data, offset + size, onChunk, callback)
})
}
Stream.prototype._read = function _read () {
var state = this._spdyState
if (!state.window.recv.isDraining()) {
return
}
var delta = state.window.recv.getDelta()
state.debug('id=%d window emptying, update by %d', this.id, delta)
state.window.recv.update(delta)
state.framer.windowUpdateFrame({
id: this.id,
delta: delta
})
}
Stream.prototype._handleData = function _handleData (frame) {
var state = this._spdyState
// DATA on ended or not readable stream!
if (!state.readable || this._readableState.ended) {
state.framer.rstFrame({ id: this.id, code: 'STREAM_CLOSED' })
return
}
state.debug('id=%d recv=%d', this.id, frame.data.length)
state.window.recv.update(-frame.data.length)
this.push(frame.data)
}
Stream.prototype._handleRST = function _handleRST (frame) {
if (frame.code !== 'CANCEL') {
this.emit('error', new Error('Got RST: ' + frame.code))
}
this.abort()
}
Stream.prototype._handleWindowUpdate = function _handleWindowUpdate (frame) {
var state = this._spdyState
state.window.send.update(frame.delta)
}
Stream.prototype._onWindowOverflow = function _onWindowOverflow () {
var state = this._spdyState
state.debug('id=%d window overflow', this.id)
state.framer.rstFrame({ id: this.id, code: 'FLOW_CONTROL_ERROR' })
this.aborted = true
this.emit('error', new Error('HTTP2 window overflow'))
}
Stream.prototype._handlePriority = function _handlePriority (frame) {
var state = this._spdyState
state.priority.remove()
state.priority = null
this._initPriority(frame.priority)
// Mostly for testing purposes
this.emit('priority', frame.priority)
}
Stream.prototype._handleHeaders = function _handleHeaders (frame) {
var state = this._spdyState
if (!state.readable || this._readableState.ended) {
state.framer.rstFrame({ id: this.id, code: 'STREAM_CLOSED' })
return
}
if (state.needResponse) {
return this._handleResponse(frame)
}
this.emit('headers', frame.headers)
}
Stream.prototype._handleResponse = function _handleResponse (frame) {
var state = this._spdyState
if (frame.headers[':status'] === undefined) {
state.framer.rstFrame({ id: this.id, code: 'PROTOCOL_ERROR' })
return
}
state.needResponse = false
this.emit('response', frame.headers[':status'] | 0, frame.headers)
}
Stream.prototype._onFinish = function _onFinish () {
var state = this._spdyState
// Send the request if it wasn't sent
if (!state.sent) {
// NOTE: will send HEADERS with FIN flag
this.send()
} else {
// Just an `.end()` without any writes will trigger immediate `finish` event
// without any calls to `_write()`.
if (state.corked !== 0) {
var self = this
state.corkQueue.push(function () {
self._onFinish()
})
return
}
state.framer.dataFrame({
id: this.id,
priority: state.priority.getPriority(),
fin: true,
data: Buffer.alloc(0)
})
}
this._maybeClose()
}
Stream.prototype._onEnd = function _onEnd () {
this._maybeClose()
}
Stream.prototype._checkEnded = function _checkEnded (callback) {
var state = this._spdyState
var ended = false
if (state.aborted) { ended = true }
if (!state.writable || this._writableState.finished) { ended = true }
if (!ended) {
return true
}
if (!callback) {
return false
}
var err = new Error('Ended stream can\'t send frames')
process.nextTick(function () {
callback(err)
})
return false
}
Stream.prototype._maybeClose = function _maybeClose () {
var state = this._spdyState
// .abort() emits `close`
if (state.aborted) {
return
}
if ((!state.readable || this._readableState.ended) &&
this._writableState.finished) {
// Clear timeout
state.timeout.set(0)
this.emit('close')
}
}
Stream.prototype._handlePushPromise = function _handlePushPromise (frame) {
var push = this.connection._createStream({
id: frame.promisedId,
parent: this,
push: true,
request: true,
method: frame.headers[':method'],
path: frame.headers[':path'],
host: frame.headers[':authority'],
priority: frame.priority,
headers: frame.headers,
writable: false
})
// GOAWAY
if (this.connection._isGoaway(push.id)) {
return
}
if (!this.emit('pushPromise', push)) {
push.abort()
}
}
Stream.prototype._hardCork = function _hardCork () {
var state = this._spdyState
this.cork()
state.corked++
}
Stream.prototype._hardUncork = function _hardUncork () {
var state = this._spdyState
this.uncork()
state.corked--
if (state.corked !== 0) {
return
}
// Invoke callbacks
var queue = state.corkQueue
state.corkQueue = []
for (var i = 0; i < queue.length; i++) {
queue[i]()
}
}
Stream.prototype._sendPush = function _sendPush (status, response, callback) {
var self = this
var state = this._spdyState
this._hardCork()
state.framer.pushFrame({
id: this.parent.id,
promisedId: this.id,
priority: state.priority.toJSON(),
path: this.path,
host: this.host,
method: this.method,
status: status,
headers: this.headers,
response: response
}, function (err) {
self._hardUncork()
callback(err)
})
}
Stream.prototype._wasSent = function _wasSent () {
var state = this._spdyState
return state.sent
}
// Public API
Stream.prototype.send = function send (callback) {
var state = this._spdyState
if (state.sent) {
var err = new Error('Stream was already sent')
process.nextTick(function () {
if (callback) {
callback(err)
}
})
return
}
state.sent = true
state.timeout.reset()
// GET requests should always be auto-finished
if (this.method === 'GET') {
this._writableState.ended = true
this._writableState.finished = true
}
// TODO(indunty): ideally it should just take a stream object as an input
var self = this
this._hardCork()
state.framer.requestFrame({
id: this.id,
method: this.method,
path: this.path,
host: this.host,
priority: state.priority.toJSON(),
headers: this.headers,
fin: this._writableState.finished
}, function (err) {
self._hardUncork()
if (!callback) {
return
}
callback(err)
})
}
Stream.prototype.respond = function respond (status, headers, callback) {
var self = this
var state = this._spdyState
assert(!state.request, 'Can\'t respond on request')
state.timeout.reset()
if (!this._checkEnded(callback)) { return }
var frame = {
id: this.id,
status: status,
headers: headers
}
this._hardCork()
state.framer.responseFrame(frame, function (err) {
self._hardUncork()
if (callback) { callback(err) }
})
}
Stream.prototype.setWindow = function setWindow (size) {
var state = this._spdyState
state.timeout.reset()
if (!this._checkEnded()) {
return
}
state.debug('id=%d force window max=%d', this.id, size)
state.window.recv.setMax(size)
var delta = state.window.recv.getDelta()
if (delta === 0) { return }
state.framer.windowUpdateFrame({
id: this.id,
delta: delta
})
state.window.recv.update(delta)
}
Stream.prototype.sendHeaders = function sendHeaders (headers, callback) {
var self = this
var state = this._spdyState
state.timeout.reset()
if (!this._checkEnded(callback)) {
return
}
// Request wasn't yet send, coalesce headers
if (!state.sent) {
this.headers = Object.assign({}, this.headers)
Object.assign(this.headers, headers)
process.nextTick(function () {
if (callback) {
callback(null)
}
})
return
}
this._hardCork()
state.framer.headersFrame({
id: this.id,
headers: headers
}, function (err) {
self._hardUncork()
if (callback) { callback(err) }
})
}
Stream.prototype._destroy = function destroy () {
this.abort()
}
Stream.prototype.abort = function abort (code, callback) {
var state = this._spdyState
// .abort(callback)
if (typeof code === 'function') {
callback = code
code = null
}
if (this._readableState.ended && this._writableState.finished) {
state.debug('id=%d already closed', this.id)
if (callback) {
process.nextTick(callback)
}
return
}
if (state.aborted) {
state.debug('id=%d already aborted', this.id)
if (callback) { process.nextTick(callback) }
return
}
state.aborted = true
state.debug('id=%d abort', this.id)
this.setTimeout(0)
var abortCode = code || 'CANCEL'
state.framer.rstFrame({
id: this.id,
code: abortCode
})
var self = this
process.nextTick(function () {
if (callback) {
callback(null)
}
self.emit('close', new Error('Aborted, code: ' + abortCode))
})
}
Stream.prototype.setPriority = function setPriority (info) {
var state = this._spdyState
state.timeout.reset()
if (!this._checkEnded()) {
return
}
state.debug('id=%d priority change', this.id, info)
var frame = { id: this.id, priority: info }
// Change priority on this side
this._handlePriority(frame)
// And on the other too
state.framer.priorityFrame(frame)
}
Stream.prototype.pushPromise = function pushPromise (uri, callback) {
if (!this._checkEnded(callback)) {
return
}
var self = this
this._hardCork()
var push = this.connection.pushPromise(this, uri, function (err) {
self._hardUncork()
if (!err) {
push._hardUncork()
}
if (callback) {
return callback(err, push)
}
if (err) { push.emit('error', err) }
})
push._hardCork()
return push
}
Stream.prototype.setMaxChunk = function setMaxChunk (size) {
var state = this._spdyState
state.maxChunk = size
}
Stream.prototype.setTimeout = function setTimeout (delay, callback) {
var state = this._spdyState
state.timeout.set(delay, callback)
}

View file

@ -0,0 +1,196 @@
'use strict'
var util = require('util')
var isNode = require('detect-node')
// Node.js 0.8, 0.10 and 0.12 support
Object.assign = (process.versions.modules >= 46 || !isNode)
? Object.assign // eslint-disable-next-line
: util._extend
function QueueItem () {
this.prev = null
this.next = null
}
exports.QueueItem = QueueItem
function Queue () {
QueueItem.call(this)
this.prev = this
this.next = this
}
util.inherits(Queue, QueueItem)
exports.Queue = Queue
Queue.prototype.insertTail = function insertTail (item) {
item.prev = this.prev
item.next = this
item.prev.next = item
item.next.prev = item
}
Queue.prototype.remove = function remove (item) {
var next = item.next
var prev = item.prev
item.next = item
item.prev = item
next.prev = prev
prev.next = next
}
Queue.prototype.head = function head () {
return this.next
}
Queue.prototype.tail = function tail () {
return this.prev
}
Queue.prototype.isEmpty = function isEmpty () {
return this.next === this
}
Queue.prototype.isRoot = function isRoot (item) {
return this === item
}
function LockStream (stream) {
this.locked = false
this.queue = []
this.stream = stream
}
exports.LockStream = LockStream
LockStream.prototype.write = function write (chunks, callback) {
var self = this
// Do not let it interleave
if (this.locked) {
this.queue.push(function () {
return self.write(chunks, callback)
})
return
}
this.locked = true
function done (err, chunks) {
self.stream.removeListener('error', done)
self.locked = false
if (self.queue.length > 0) { self.queue.shift()() }
callback(err, chunks)
}
this.stream.on('error', done)
// Accumulate all output data
var output = []
function onData (chunk) {
output.push(chunk)
}
this.stream.on('data', onData)
function next (err) {
self.stream.removeListener('data', onData)
if (err) {
return done(err)
}
done(null, output)
}
for (var i = 0; i < chunks.length - 1; i++) { this.stream.write(chunks[i]) }
if (chunks.length > 0) {
this.stream.write(chunks[i], next)
} else { process.nextTick(next) }
if (this.stream.execute) {
this.stream.execute(function (err) {
if (err) { return done(err) }
})
}
}
// Just finds the place in array to insert
function binaryLookup (list, item, compare) {
var start = 0
var end = list.length
while (start < end) {
var pos = (start + end) >> 1
var cmp = compare(item, list[pos])
if (cmp === 0) {
start = pos
end = pos
break
} else if (cmp < 0) {
end = pos
} else {
start = pos + 1
}
}
return start
}
exports.binaryLookup = binaryLookup
function binaryInsert (list, item, compare) {
var index = binaryLookup(list, item, compare)
list.splice(index, 0, item)
}
exports.binaryInsert = binaryInsert
function binarySearch (list, item, compare) {
var index = binaryLookup(list, item, compare)
if (index >= list.length) {
return -1
}
if (compare(item, list[index]) === 0) {
return index
}
return -1
}
exports.binarySearch = binarySearch
function Timeout (object) {
this.delay = 0
this.timer = null
this.object = object
}
exports.Timeout = Timeout
Timeout.prototype.set = function set (delay, callback) {
this.delay = delay
this.reset()
if (!callback) { return }
if (this.delay === 0) {
this.object.removeListener('timeout', callback)
} else {
this.object.once('timeout', callback)
}
}
Timeout.prototype.reset = function reset () {
if (this.timer !== null) {
clearTimeout(this.timer)
this.timer = null
}
if (this.delay === 0) { return }
var self = this
this.timer = setTimeout(function () {
self.timer = null
self.object.emit('timeout')
}, this.delay)
}

View file

@ -0,0 +1,174 @@
'use strict'
var util = require('util')
var EventEmitter = require('events').EventEmitter
var debug = {
server: require('debug')('spdy:window:server'),
client: require('debug')('spdy:window:client')
}
function Side (window, name, options) {
EventEmitter.call(this)
this.name = name
this.window = window
this.current = options.size
this.max = options.size
this.limit = options.max
this.lowWaterMark = options.lowWaterMark === undefined
? this.max / 2
: options.lowWaterMark
this._refilling = false
this._refillQueue = []
}
util.inherits(Side, EventEmitter)
Side.prototype.setMax = function setMax (max) {
this.window.debug('id=%d side=%s setMax=%d',
this.window.id,
this.name,
max)
this.max = max
this.lowWaterMark = this.max / 2
}
Side.prototype.updateMax = function updateMax (max) {
var delta = max - this.max
this.window.debug('id=%d side=%s updateMax=%d delta=%d',
this.window.id,
this.name,
max,
delta)
this.max = max
this.lowWaterMark = max / 2
this.update(delta)
}
Side.prototype.setLowWaterMark = function setLowWaterMark (lwm) {
this.lowWaterMark = lwm
}
Side.prototype.update = function update (size, callback) {
// Not enough space for the update, wait for refill
if (size <= 0 && callback && this.isEmpty()) {
this.window.debug('id=%d side=%s wait for refill=%d [%d/%d]',
this.window.id,
this.name,
-size,
this.current,
this.max)
this._refillQueue.push({
size: size,
callback: callback
})
return
}
this.current += size
if (this.current > this.limit) {
this.emit('overflow')
return
}
this.window.debug('id=%d side=%s update by=%d [%d/%d]',
this.window.id,
this.name,
size,
this.current,
this.max)
// Time to send WINDOW_UPDATE
if (size < 0 && this.isDraining()) {
this.window.debug('id=%d side=%s drained', this.window.id, this.name)
this.emit('drain')
}
// Time to write
if (size > 0 && this.current > 0 && this.current <= size) {
this.window.debug('id=%d side=%s full', this.window.id, this.name)
this.emit('full')
}
this._processRefillQueue()
if (callback) { process.nextTick(callback) }
}
Side.prototype.getCurrent = function getCurrent () {
return this.current
}
Side.prototype.getMax = function getMax () {
return this.max
}
Side.prototype.getDelta = function getDelta () {
return this.max - this.current
}
Side.prototype.isDraining = function isDraining () {
return this.current <= this.lowWaterMark
}
Side.prototype.isEmpty = function isEmpty () {
return this.current <= 0
}
// Private
Side.prototype._processRefillQueue = function _processRefillQueue () {
// Prevent recursion
if (this._refilling) {
return
}
this._refilling = true
while (this._refillQueue.length > 0) {
var item = this._refillQueue[0]
if (this.isEmpty()) {
break
}
this.window.debug('id=%d side=%s refilled for size=%d',
this.window.id,
this.name,
-item.size)
this._refillQueue.shift()
this.update(item.size, item.callback)
}
this._refilling = false
}
function Window (options) {
this.id = options.id
this.isServer = options.isServer
this.debug = this.isServer ? debug.server : debug.client
this.recv = new Side(this, 'recv', options.recv)
this.send = new Side(this, 'send', options.send)
}
module.exports = Window
Window.prototype.clone = function clone (id) {
return new Window({
id: id,
isServer: this.isServer,
recv: {
size: this.recv.max,
max: this.recv.limit,
lowWaterMark: this.recv.lowWaterMark
},
send: {
size: this.send.max,
max: this.send.limit,
lowWaterMark: this.send.lowWaterMark
}
})
}

43
my-app/node_modules/spdy-transport/package.json generated vendored Executable file
View file

@ -0,0 +1,43 @@
{
"name": "spdy-transport",
"version": "3.0.0",
"main": "lib/spdy-transport",
"description": "SPDY v2, v3, v3.1 and HTTP2 transport",
"license": "MIT",
"keywords": [
"spdy",
"http2",
"transport"
],
"repository": {
"type": "git",
"url": "git://github.com/spdy-http2/spdy-transport.git"
},
"homepage": "https://github.com/spdy-http2/spdy-transport",
"author": "Fedor Indutny <fedor@indutny.com>",
"dependencies": {
"debug": "^4.1.0",
"detect-node": "^2.0.4",
"hpack.js": "^2.1.6",
"obuf": "^1.1.2",
"readable-stream": "^3.0.6",
"wbuf": "^1.7.3"
},
"devDependencies": {
"async": "^2.6.1",
"istanbul": "^0.4.5",
"mocha": "^5.2.0",
"pre-commit": "^1.2.2",
"standard": "^12.0.1",
"stream-pair": "^1.0.3"
},
"scripts": {
"lint": "standard",
"test": "mocha --reporter=spec test/**/*-test.js test/**/**/*-test.js",
"coverage": "istanbul cover node_modules/.bin/_mocha -- --reporter=spec test/**/*-test.js test/**/**/*-test.js"
},
"pre-commit": [
"lint",
"test"
]
}