first commit

This commit is contained in:
Stefan Hacker
2026-04-03 09:38:48 +02:00
commit 37ad745546
47450 changed files with 3120798 additions and 0 deletions
+14
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
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
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
+845
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)
}
+188
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--
}
@@ -0,0 +1,4 @@
exports.DEFAULT_METHOD = 'GET'
exports.DEFAULT_HOST = 'localhost'
exports.MAX_PRIORITY_STREAMS = 100
exports.DEFAULT_MAX_CHUNK = 8 * 1024
+58
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()
}
}
@@ -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')
+106
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)
}
@@ -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
}
+94
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
}
}
}
@@ -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'
+542
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)
}
@@ -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 () {
}
@@ -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')
+578
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()
})
}
@@ -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
}
@@ -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]
+519
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)
}
@@ -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')
+485
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()
})
}
@@ -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)
}
+710
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)
}
+196
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)
}
+174
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
}
})
}
+20
View File
@@ -0,0 +1,20 @@
(The MIT License)
Copyright (c) 2014-2017 TJ Holowaychuk <tj@vision-media.ca>
Copyright (c) 2018-2021 Josh Junon
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.
+481
View File
@@ -0,0 +1,481 @@
# debug
[![OpenCollective](https://opencollective.com/debug/backers/badge.svg)](#backers)
[![OpenCollective](https://opencollective.com/debug/sponsors/badge.svg)](#sponsors)
<img width="647" src="https://user-images.githubusercontent.com/71256/29091486-fa38524c-7c37-11e7-895f-e7ec8e1039b6.png">
A tiny JavaScript debugging utility modelled after Node.js core's debugging
technique. Works in Node.js and web browsers.
## Installation
```bash
$ npm install debug
```
## Usage
`debug` exposes a function; simply pass this function the name of your module, and it will return a decorated version of `console.error` for you to pass debug statements to. This will allow you to toggle the debug output for different parts of your module as well as the module as a whole.
Example [_app.js_](./examples/node/app.js):
```js
var debug = require('debug')('http')
, http = require('http')
, name = 'My App';
// fake app
debug('booting %o', name);
http.createServer(function(req, res){
debug(req.method + ' ' + req.url);
res.end('hello\n');
}).listen(3000, function(){
debug('listening');
});
// fake worker of some kind
require('./worker');
```
Example [_worker.js_](./examples/node/worker.js):
```js
var a = require('debug')('worker:a')
, b = require('debug')('worker:b');
function work() {
a('doing lots of uninteresting work');
setTimeout(work, Math.random() * 1000);
}
work();
function workb() {
b('doing some work');
setTimeout(workb, Math.random() * 2000);
}
workb();
```
The `DEBUG` environment variable is then used to enable these based on space or
comma-delimited names.
Here are some examples:
<img width="647" alt="screen shot 2017-08-08 at 12 53 04 pm" src="https://user-images.githubusercontent.com/71256/29091703-a6302cdc-7c38-11e7-8304-7c0b3bc600cd.png">
<img width="647" alt="screen shot 2017-08-08 at 12 53 38 pm" src="https://user-images.githubusercontent.com/71256/29091700-a62a6888-7c38-11e7-800b-db911291ca2b.png">
<img width="647" alt="screen shot 2017-08-08 at 12 53 25 pm" src="https://user-images.githubusercontent.com/71256/29091701-a62ea114-7c38-11e7-826a-2692bedca740.png">
#### Windows command prompt notes
##### CMD
On Windows the environment variable is set using the `set` command.
```cmd
set DEBUG=*,-not_this
```
Example:
```cmd
set DEBUG=* & node app.js
```
##### PowerShell (VS Code default)
PowerShell uses different syntax to set environment variables.
```cmd
$env:DEBUG = "*,-not_this"
```
Example:
```cmd
$env:DEBUG='app';node app.js
```
Then, run the program to be debugged as usual.
npm script example:
```js
"windowsDebug": "@powershell -Command $env:DEBUG='*';node app.js",
```
## Namespace Colors
Every debug instance has a color generated for it based on its namespace name.
This helps when visually parsing the debug output to identify which debug instance
a debug line belongs to.
#### Node.js
In Node.js, colors are enabled when stderr is a TTY. You also _should_ install
the [`supports-color`](https://npmjs.org/supports-color) module alongside debug,
otherwise debug will only use a small handful of basic colors.
<img width="521" src="https://user-images.githubusercontent.com/71256/29092181-47f6a9e6-7c3a-11e7-9a14-1928d8a711cd.png">
#### Web Browser
Colors are also enabled on "Web Inspectors" that understand the `%c` formatting
option. These are WebKit web inspectors, Firefox ([since version
31](https://hacks.mozilla.org/2014/05/editable-box-model-multiple-selection-sublime-text-keys-much-more-firefox-developer-tools-episode-31/))
and the Firebug plugin for Firefox (any version).
<img width="524" src="https://user-images.githubusercontent.com/71256/29092033-b65f9f2e-7c39-11e7-8e32-f6f0d8e865c1.png">
## Millisecond diff
When actively developing an application it can be useful to see when the time spent between one `debug()` call and the next. Suppose for example you invoke `debug()` before requesting a resource, and after as well, the "+NNNms" will show you how much time was spent between calls.
<img width="647" src="https://user-images.githubusercontent.com/71256/29091486-fa38524c-7c37-11e7-895f-e7ec8e1039b6.png">
When stdout is not a TTY, `Date#toISOString()` is used, making it more useful for logging the debug information as shown below:
<img width="647" src="https://user-images.githubusercontent.com/71256/29091956-6bd78372-7c39-11e7-8c55-c948396d6edd.png">
## Conventions
If you're using this in one or more of your libraries, you _should_ use the name of your library so that developers may toggle debugging as desired without guessing names. If you have more than one debuggers you _should_ prefix them with your library name and use ":" to separate features. For example "bodyParser" from Connect would then be "connect:bodyParser". If you append a "*" to the end of your name, it will always be enabled regardless of the setting of the DEBUG environment variable. You can then use it for normal output as well as debug output.
## Wildcards
The `*` character may be used as a wildcard. Suppose for example your library has
debuggers named "connect:bodyParser", "connect:compress", "connect:session",
instead of listing all three with
`DEBUG=connect:bodyParser,connect:compress,connect:session`, you may simply do
`DEBUG=connect:*`, or to run everything using this module simply use `DEBUG=*`.
You can also exclude specific debuggers by prefixing them with a "-" character.
For example, `DEBUG=*,-connect:*` would include all debuggers except those
starting with "connect:".
## Environment Variables
When running through Node.js, you can set a few environment variables that will
change the behavior of the debug logging:
| Name | Purpose |
|-----------|-------------------------------------------------|
| `DEBUG` | Enables/disables specific debugging namespaces. |
| `DEBUG_HIDE_DATE` | Hide date from debug output (non-TTY). |
| `DEBUG_COLORS`| Whether or not to use colors in the debug output. |
| `DEBUG_DEPTH` | Object inspection depth. |
| `DEBUG_SHOW_HIDDEN` | Shows hidden properties on inspected objects. |
__Note:__ The environment variables beginning with `DEBUG_` end up being
converted into an Options object that gets used with `%o`/`%O` formatters.
See the Node.js documentation for
[`util.inspect()`](https://nodejs.org/api/util.html#util_util_inspect_object_options)
for the complete list.
## Formatters
Debug uses [printf-style](https://wikipedia.org/wiki/Printf_format_string) formatting.
Below are the officially supported formatters:
| Formatter | Representation |
|-----------|----------------|
| `%O` | Pretty-print an Object on multiple lines. |
| `%o` | Pretty-print an Object all on a single line. |
| `%s` | String. |
| `%d` | Number (both integer and float). |
| `%j` | JSON. Replaced with the string '[Circular]' if the argument contains circular references. |
| `%%` | Single percent sign ('%'). This does not consume an argument. |
### Custom formatters
You can add custom formatters by extending the `debug.formatters` object.
For example, if you wanted to add support for rendering a Buffer as hex with
`%h`, you could do something like:
```js
const createDebug = require('debug')
createDebug.formatters.h = (v) => {
return v.toString('hex')
}
// …elsewhere
const debug = createDebug('foo')
debug('this is hex: %h', new Buffer('hello world'))
// foo this is hex: 68656c6c6f20776f726c6421 +0ms
```
## Browser Support
You can build a browser-ready script using [browserify](https://github.com/substack/node-browserify),
or just use the [browserify-as-a-service](https://wzrd.in/) [build](https://wzrd.in/standalone/debug@latest),
if you don't want to build it yourself.
Debug's enable state is currently persisted by `localStorage`.
Consider the situation shown below where you have `worker:a` and `worker:b`,
and wish to debug both. You can enable this using `localStorage.debug`:
```js
localStorage.debug = 'worker:*'
```
And then refresh the page.
```js
a = debug('worker:a');
b = debug('worker:b');
setInterval(function(){
a('doing some work');
}, 1000);
setInterval(function(){
b('doing some work');
}, 1200);
```
In Chromium-based web browsers (e.g. Brave, Chrome, and Electron), the JavaScript console will—by default—only show messages logged by `debug` if the "Verbose" log level is _enabled_.
<img width="647" src="https://user-images.githubusercontent.com/7143133/152083257-29034707-c42c-4959-8add-3cee850e6fcf.png">
## Output streams
By default `debug` will log to stderr, however this can be configured per-namespace by overriding the `log` method:
Example [_stdout.js_](./examples/node/stdout.js):
```js
var debug = require('debug');
var error = debug('app:error');
// by default stderr is used
error('goes to stderr!');
var log = debug('app:log');
// set this namespace to log via console.log
log.log = console.log.bind(console); // don't forget to bind to console!
log('goes to stdout');
error('still goes to stderr!');
// set all output to go via console.info
// overrides all per-namespace log settings
debug.log = console.info.bind(console);
error('now goes to stdout via console.info');
log('still goes to stdout, but via console.info now');
```
## Extend
You can simply extend debugger
```js
const log = require('debug')('auth');
//creates new debug instance with extended namespace
const logSign = log.extend('sign');
const logLogin = log.extend('login');
log('hello'); // auth hello
logSign('hello'); //auth:sign hello
logLogin('hello'); //auth:login hello
```
## Set dynamically
You can also enable debug dynamically by calling the `enable()` method :
```js
let debug = require('debug');
console.log(1, debug.enabled('test'));
debug.enable('test');
console.log(2, debug.enabled('test'));
debug.disable();
console.log(3, debug.enabled('test'));
```
print :
```
1 false
2 true
3 false
```
Usage :
`enable(namespaces)`
`namespaces` can include modes separated by a colon and wildcards.
Note that calling `enable()` completely overrides previously set DEBUG variable :
```
$ DEBUG=foo node -e 'var dbg = require("debug"); dbg.enable("bar"); console.log(dbg.enabled("foo"))'
=> false
```
`disable()`
Will disable all namespaces. The functions returns the namespaces currently
enabled (and skipped). This can be useful if you want to disable debugging
temporarily without knowing what was enabled to begin with.
For example:
```js
let debug = require('debug');
debug.enable('foo:*,-foo:bar');
let namespaces = debug.disable();
debug.enable(namespaces);
```
Note: There is no guarantee that the string will be identical to the initial
enable string, but semantically they will be identical.
## Checking whether a debug target is enabled
After you've created a debug instance, you can determine whether or not it is
enabled by checking the `enabled` property:
```javascript
const debug = require('debug')('http');
if (debug.enabled) {
// do stuff...
}
```
You can also manually toggle this property to force the debug instance to be
enabled or disabled.
## Usage in child processes
Due to the way `debug` detects if the output is a TTY or not, colors are not shown in child processes when `stderr` is piped. A solution is to pass the `DEBUG_COLORS=1` environment variable to the child process.
For example:
```javascript
worker = fork(WORKER_WRAP_PATH, [workerPath], {
stdio: [
/* stdin: */ 0,
/* stdout: */ 'pipe',
/* stderr: */ 'pipe',
'ipc',
],
env: Object.assign({}, process.env, {
DEBUG_COLORS: 1 // without this settings, colors won't be shown
}),
});
worker.stderr.pipe(process.stderr, { end: false });
```
## Authors
- TJ Holowaychuk
- Nathan Rajlich
- Andrew Rhyne
- Josh Junon
## Backers
Support us with a monthly donation and help us continue our activities. [[Become a backer](https://opencollective.com/debug#backer)]
<a href="https://opencollective.com/debug/backer/0/website" target="_blank"><img src="https://opencollective.com/debug/backer/0/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/1/website" target="_blank"><img src="https://opencollective.com/debug/backer/1/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/2/website" target="_blank"><img src="https://opencollective.com/debug/backer/2/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/3/website" target="_blank"><img src="https://opencollective.com/debug/backer/3/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/4/website" target="_blank"><img src="https://opencollective.com/debug/backer/4/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/5/website" target="_blank"><img src="https://opencollective.com/debug/backer/5/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/6/website" target="_blank"><img src="https://opencollective.com/debug/backer/6/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/7/website" target="_blank"><img src="https://opencollective.com/debug/backer/7/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/8/website" target="_blank"><img src="https://opencollective.com/debug/backer/8/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/9/website" target="_blank"><img src="https://opencollective.com/debug/backer/9/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/10/website" target="_blank"><img src="https://opencollective.com/debug/backer/10/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/11/website" target="_blank"><img src="https://opencollective.com/debug/backer/11/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/12/website" target="_blank"><img src="https://opencollective.com/debug/backer/12/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/13/website" target="_blank"><img src="https://opencollective.com/debug/backer/13/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/14/website" target="_blank"><img src="https://opencollective.com/debug/backer/14/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/15/website" target="_blank"><img src="https://opencollective.com/debug/backer/15/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/16/website" target="_blank"><img src="https://opencollective.com/debug/backer/16/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/17/website" target="_blank"><img src="https://opencollective.com/debug/backer/17/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/18/website" target="_blank"><img src="https://opencollective.com/debug/backer/18/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/19/website" target="_blank"><img src="https://opencollective.com/debug/backer/19/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/20/website" target="_blank"><img src="https://opencollective.com/debug/backer/20/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/21/website" target="_blank"><img src="https://opencollective.com/debug/backer/21/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/22/website" target="_blank"><img src="https://opencollective.com/debug/backer/22/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/23/website" target="_blank"><img src="https://opencollective.com/debug/backer/23/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/24/website" target="_blank"><img src="https://opencollective.com/debug/backer/24/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/25/website" target="_blank"><img src="https://opencollective.com/debug/backer/25/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/26/website" target="_blank"><img src="https://opencollective.com/debug/backer/26/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/27/website" target="_blank"><img src="https://opencollective.com/debug/backer/27/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/28/website" target="_blank"><img src="https://opencollective.com/debug/backer/28/avatar.svg"></a>
<a href="https://opencollective.com/debug/backer/29/website" target="_blank"><img src="https://opencollective.com/debug/backer/29/avatar.svg"></a>
## Sponsors
Become a sponsor and get your logo on our README on Github with a link to your site. [[Become a sponsor](https://opencollective.com/debug#sponsor)]
<a href="https://opencollective.com/debug/sponsor/0/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/1/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/2/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/3/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/4/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/5/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/6/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/7/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/8/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/9/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/9/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/10/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/10/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/11/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/11/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/12/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/12/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/13/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/13/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/14/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/14/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/15/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/15/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/16/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/16/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/17/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/17/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/18/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/18/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/19/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/19/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/20/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/20/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/21/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/21/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/22/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/22/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/23/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/23/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/24/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/24/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/25/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/25/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/26/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/26/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/27/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/27/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/28/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/28/avatar.svg"></a>
<a href="https://opencollective.com/debug/sponsor/29/website" target="_blank"><img src="https://opencollective.com/debug/sponsor/29/avatar.svg"></a>
## License
(The MIT License)
Copyright (c) 2014-2017 TJ Holowaychuk &lt;tj@vision-media.ca&gt;
Copyright (c) 2018-2021 Josh Junon
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.
+64
View File
@@ -0,0 +1,64 @@
{
"name": "debug",
"version": "4.4.3",
"repository": {
"type": "git",
"url": "git://github.com/debug-js/debug.git"
},
"description": "Lightweight debugging utility for Node.js and the browser",
"keywords": [
"debug",
"log",
"debugger"
],
"files": [
"src",
"LICENSE",
"README.md"
],
"author": "Josh Junon (https://github.com/qix-)",
"contributors": [
"TJ Holowaychuk <tj@vision-media.ca>",
"Nathan Rajlich <nathan@tootallnate.net> (http://n8.io)",
"Andrew Rhyne <rhyneandrew@gmail.com>"
],
"license": "MIT",
"scripts": {
"lint": "xo",
"test": "npm run test:node && npm run test:browser && npm run lint",
"test:node": "mocha test.js test.node.js",
"test:browser": "karma start --single-run",
"test:coverage": "cat ./coverage/lcov.info | coveralls"
},
"dependencies": {
"ms": "^2.1.3"
},
"devDependencies": {
"brfs": "^2.0.1",
"browserify": "^16.2.3",
"coveralls": "^3.0.2",
"karma": "^3.1.4",
"karma-browserify": "^6.0.0",
"karma-chrome-launcher": "^2.2.0",
"karma-mocha": "^1.3.0",
"mocha": "^5.2.0",
"mocha-lcov-reporter": "^1.2.0",
"sinon": "^14.0.0",
"xo": "^0.23.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
},
"main": "./src/index.js",
"browser": "./src/browser.js",
"engines": {
"node": ">=6.0"
},
"xo": {
"rules": {
"import/extensions": "off"
}
}
}
+272
View File
@@ -0,0 +1,272 @@
/* eslint-env browser */
/**
* This is the web browser implementation of `debug()`.
*/
exports.formatArgs = formatArgs;
exports.save = save;
exports.load = load;
exports.useColors = useColors;
exports.storage = localstorage();
exports.destroy = (() => {
let warned = false;
return () => {
if (!warned) {
warned = true;
console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.');
}
};
})();
/**
* Colors.
*/
exports.colors = [
'#0000CC',
'#0000FF',
'#0033CC',
'#0033FF',
'#0066CC',
'#0066FF',
'#0099CC',
'#0099FF',
'#00CC00',
'#00CC33',
'#00CC66',
'#00CC99',
'#00CCCC',
'#00CCFF',
'#3300CC',
'#3300FF',
'#3333CC',
'#3333FF',
'#3366CC',
'#3366FF',
'#3399CC',
'#3399FF',
'#33CC00',
'#33CC33',
'#33CC66',
'#33CC99',
'#33CCCC',
'#33CCFF',
'#6600CC',
'#6600FF',
'#6633CC',
'#6633FF',
'#66CC00',
'#66CC33',
'#9900CC',
'#9900FF',
'#9933CC',
'#9933FF',
'#99CC00',
'#99CC33',
'#CC0000',
'#CC0033',
'#CC0066',
'#CC0099',
'#CC00CC',
'#CC00FF',
'#CC3300',
'#CC3333',
'#CC3366',
'#CC3399',
'#CC33CC',
'#CC33FF',
'#CC6600',
'#CC6633',
'#CC9900',
'#CC9933',
'#CCCC00',
'#CCCC33',
'#FF0000',
'#FF0033',
'#FF0066',
'#FF0099',
'#FF00CC',
'#FF00FF',
'#FF3300',
'#FF3333',
'#FF3366',
'#FF3399',
'#FF33CC',
'#FF33FF',
'#FF6600',
'#FF6633',
'#FF9900',
'#FF9933',
'#FFCC00',
'#FFCC33'
];
/**
* Currently only WebKit-based Web Inspectors, Firefox >= v31,
* and the Firebug extension (any Firefox version) are known
* to support "%c" CSS customizations.
*
* TODO: add a `localStorage` variable to explicitly enable/disable colors
*/
// eslint-disable-next-line complexity
function useColors() {
// NB: In an Electron preload script, document will be defined but not fully
// initialized. Since we know we're in Chrome, we'll just detect this case
// explicitly
if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) {
return true;
}
// Internet Explorer and Edge do not support colors.
if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) {
return false;
}
let m;
// Is webkit? http://stackoverflow.com/a/16459606/376773
// document is undefined in react-native: https://github.com/facebook/react-native/pull/1632
// eslint-disable-next-line no-return-assign
return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) ||
// Is firebug? http://stackoverflow.com/a/398120/376773
(typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) ||
// Is firefox >= v31?
// https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
(typeof navigator !== 'undefined' && navigator.userAgent && (m = navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)) && parseInt(m[1], 10) >= 31) ||
// Double check webkit in userAgent just in case we are in a worker
(typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/));
}
/**
* Colorize log arguments if enabled.
*
* @api public
*/
function formatArgs(args) {
args[0] = (this.useColors ? '%c' : '') +
this.namespace +
(this.useColors ? ' %c' : ' ') +
args[0] +
(this.useColors ? '%c ' : ' ') +
'+' + module.exports.humanize(this.diff);
if (!this.useColors) {
return;
}
const c = 'color: ' + this.color;
args.splice(1, 0, c, 'color: inherit');
// The final "%c" is somewhat tricky, because there could be other
// arguments passed either before or after the %c, so we need to
// figure out the correct index to insert the CSS into
let index = 0;
let lastC = 0;
args[0].replace(/%[a-zA-Z%]/g, match => {
if (match === '%%') {
return;
}
index++;
if (match === '%c') {
// We only are interested in the *last* %c
// (the user may have provided their own)
lastC = index;
}
});
args.splice(lastC, 0, c);
}
/**
* Invokes `console.debug()` when available.
* No-op when `console.debug` is not a "function".
* If `console.debug` is not available, falls back
* to `console.log`.
*
* @api public
*/
exports.log = console.debug || console.log || (() => {});
/**
* Save `namespaces`.
*
* @param {String} namespaces
* @api private
*/
function save(namespaces) {
try {
if (namespaces) {
exports.storage.setItem('debug', namespaces);
} else {
exports.storage.removeItem('debug');
}
} catch (error) {
// Swallow
// XXX (@Qix-) should we be logging these?
}
}
/**
* Load `namespaces`.
*
* @return {String} returns the previously persisted debug modes
* @api private
*/
function load() {
let r;
try {
r = exports.storage.getItem('debug') || exports.storage.getItem('DEBUG') ;
} catch (error) {
// Swallow
// XXX (@Qix-) should we be logging these?
}
// If debug isn't set in LS, and we're in Electron, try to load $DEBUG
if (!r && typeof process !== 'undefined' && 'env' in process) {
r = process.env.DEBUG;
}
return r;
}
/**
* Localstorage attempts to return the localstorage.
*
* This is necessary because safari throws
* when a user disables cookies/localstorage
* and you attempt to access it.
*
* @return {LocalStorage}
* @api private
*/
function localstorage() {
try {
// TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context
// The Browser also has localStorage in the global context.
return localStorage;
} catch (error) {
// Swallow
// XXX (@Qix-) should we be logging these?
}
}
module.exports = require('./common')(exports);
const {formatters} = module.exports;
/**
* Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
*/
formatters.j = function (v) {
try {
return JSON.stringify(v);
} catch (error) {
return '[UnexpectedJSONParseError]: ' + error.message;
}
};
+292
View File
@@ -0,0 +1,292 @@
/**
* This is the common logic for both the Node.js and web browser
* implementations of `debug()`.
*/
function setup(env) {
createDebug.debug = createDebug;
createDebug.default = createDebug;
createDebug.coerce = coerce;
createDebug.disable = disable;
createDebug.enable = enable;
createDebug.enabled = enabled;
createDebug.humanize = require('ms');
createDebug.destroy = destroy;
Object.keys(env).forEach(key => {
createDebug[key] = env[key];
});
/**
* The currently active debug mode names, and names to skip.
*/
createDebug.names = [];
createDebug.skips = [];
/**
* Map of special "%n" handling functions, for the debug "format" argument.
*
* Valid key names are a single, lower or upper-case letter, i.e. "n" and "N".
*/
createDebug.formatters = {};
/**
* Selects a color for a debug namespace
* @param {String} namespace The namespace string for the debug instance to be colored
* @return {Number|String} An ANSI color code for the given namespace
* @api private
*/
function selectColor(namespace) {
let hash = 0;
for (let i = 0; i < namespace.length; i++) {
hash = ((hash << 5) - hash) + namespace.charCodeAt(i);
hash |= 0; // Convert to 32bit integer
}
return createDebug.colors[Math.abs(hash) % createDebug.colors.length];
}
createDebug.selectColor = selectColor;
/**
* Create a debugger with the given `namespace`.
*
* @param {String} namespace
* @return {Function}
* @api public
*/
function createDebug(namespace) {
let prevTime;
let enableOverride = null;
let namespacesCache;
let enabledCache;
function debug(...args) {
// Disabled?
if (!debug.enabled) {
return;
}
const self = debug;
// Set `diff` timestamp
const curr = Number(new Date());
const ms = curr - (prevTime || curr);
self.diff = ms;
self.prev = prevTime;
self.curr = curr;
prevTime = curr;
args[0] = createDebug.coerce(args[0]);
if (typeof args[0] !== 'string') {
// Anything else let's inspect with %O
args.unshift('%O');
}
// Apply any `formatters` transformations
let index = 0;
args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => {
// If we encounter an escaped % then don't increase the array index
if (match === '%%') {
return '%';
}
index++;
const formatter = createDebug.formatters[format];
if (typeof formatter === 'function') {
const val = args[index];
match = formatter.call(self, val);
// Now we need to remove `args[index]` since it's inlined in the `format`
args.splice(index, 1);
index--;
}
return match;
});
// Apply env-specific formatting (colors, etc.)
createDebug.formatArgs.call(self, args);
const logFn = self.log || createDebug.log;
logFn.apply(self, args);
}
debug.namespace = namespace;
debug.useColors = createDebug.useColors();
debug.color = createDebug.selectColor(namespace);
debug.extend = extend;
debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release.
Object.defineProperty(debug, 'enabled', {
enumerable: true,
configurable: false,
get: () => {
if (enableOverride !== null) {
return enableOverride;
}
if (namespacesCache !== createDebug.namespaces) {
namespacesCache = createDebug.namespaces;
enabledCache = createDebug.enabled(namespace);
}
return enabledCache;
},
set: v => {
enableOverride = v;
}
});
// Env-specific initialization logic for debug instances
if (typeof createDebug.init === 'function') {
createDebug.init(debug);
}
return debug;
}
function extend(namespace, delimiter) {
const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace);
newDebug.log = this.log;
return newDebug;
}
/**
* Enables a debug mode by namespaces. This can include modes
* separated by a colon and wildcards.
*
* @param {String} namespaces
* @api public
*/
function enable(namespaces) {
createDebug.save(namespaces);
createDebug.namespaces = namespaces;
createDebug.names = [];
createDebug.skips = [];
const split = (typeof namespaces === 'string' ? namespaces : '')
.trim()
.replace(/\s+/g, ',')
.split(',')
.filter(Boolean);
for (const ns of split) {
if (ns[0] === '-') {
createDebug.skips.push(ns.slice(1));
} else {
createDebug.names.push(ns);
}
}
}
/**
* Checks if the given string matches a namespace template, honoring
* asterisks as wildcards.
*
* @param {String} search
* @param {String} template
* @return {Boolean}
*/
function matchesTemplate(search, template) {
let searchIndex = 0;
let templateIndex = 0;
let starIndex = -1;
let matchIndex = 0;
while (searchIndex < search.length) {
if (templateIndex < template.length && (template[templateIndex] === search[searchIndex] || template[templateIndex] === '*')) {
// Match character or proceed with wildcard
if (template[templateIndex] === '*') {
starIndex = templateIndex;
matchIndex = searchIndex;
templateIndex++; // Skip the '*'
} else {
searchIndex++;
templateIndex++;
}
} else if (starIndex !== -1) { // eslint-disable-line no-negated-condition
// Backtrack to the last '*' and try to match more characters
templateIndex = starIndex + 1;
matchIndex++;
searchIndex = matchIndex;
} else {
return false; // No match
}
}
// Handle trailing '*' in template
while (templateIndex < template.length && template[templateIndex] === '*') {
templateIndex++;
}
return templateIndex === template.length;
}
/**
* Disable debug output.
*
* @return {String} namespaces
* @api public
*/
function disable() {
const namespaces = [
...createDebug.names,
...createDebug.skips.map(namespace => '-' + namespace)
].join(',');
createDebug.enable('');
return namespaces;
}
/**
* Returns true if the given mode name is enabled, false otherwise.
*
* @param {String} name
* @return {Boolean}
* @api public
*/
function enabled(name) {
for (const skip of createDebug.skips) {
if (matchesTemplate(name, skip)) {
return false;
}
}
for (const ns of createDebug.names) {
if (matchesTemplate(name, ns)) {
return true;
}
}
return false;
}
/**
* Coerce `val`.
*
* @param {Mixed} val
* @return {Mixed}
* @api private
*/
function coerce(val) {
if (val instanceof Error) {
return val.stack || val.message;
}
return val;
}
/**
* XXX DO NOT USE. This is a temporary stub function.
* XXX It WILL be removed in the next major release.
*/
function destroy() {
console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.');
}
createDebug.enable(createDebug.load());
return createDebug;
}
module.exports = setup;
+10
View File
@@ -0,0 +1,10 @@
/**
* Detect Electron renderer / nwjs process, which is node, but we should
* treat as a browser.
*/
if (typeof process === 'undefined' || process.type === 'renderer' || process.browser === true || process.__nwjs) {
module.exports = require('./browser.js');
} else {
module.exports = require('./node.js');
}
+263
View File
@@ -0,0 +1,263 @@
/**
* Module dependencies.
*/
const tty = require('tty');
const util = require('util');
/**
* This is the Node.js implementation of `debug()`.
*/
exports.init = init;
exports.log = log;
exports.formatArgs = formatArgs;
exports.save = save;
exports.load = load;
exports.useColors = useColors;
exports.destroy = util.deprecate(
() => {},
'Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'
);
/**
* Colors.
*/
exports.colors = [6, 2, 3, 4, 5, 1];
try {
// Optional dependency (as in, doesn't need to be installed, NOT like optionalDependencies in package.json)
// eslint-disable-next-line import/no-extraneous-dependencies
const supportsColor = require('supports-color');
if (supportsColor && (supportsColor.stderr || supportsColor).level >= 2) {
exports.colors = [
20,
21,
26,
27,
32,
33,
38,
39,
40,
41,
42,
43,
44,
45,
56,
57,
62,
63,
68,
69,
74,
75,
76,
77,
78,
79,
80,
81,
92,
93,
98,
99,
112,
113,
128,
129,
134,
135,
148,
149,
160,
161,
162,
163,
164,
165,
166,
167,
168,
169,
170,
171,
172,
173,
178,
179,
184,
185,
196,
197,
198,
199,
200,
201,
202,
203,
204,
205,
206,
207,
208,
209,
214,
215,
220,
221
];
}
} catch (error) {
// Swallow - we only care if `supports-color` is available; it doesn't have to be.
}
/**
* Build up the default `inspectOpts` object from the environment variables.
*
* $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js
*/
exports.inspectOpts = Object.keys(process.env).filter(key => {
return /^debug_/i.test(key);
}).reduce((obj, key) => {
// Camel-case
const prop = key
.substring(6)
.toLowerCase()
.replace(/_([a-z])/g, (_, k) => {
return k.toUpperCase();
});
// Coerce string value into JS value
let val = process.env[key];
if (/^(yes|on|true|enabled)$/i.test(val)) {
val = true;
} else if (/^(no|off|false|disabled)$/i.test(val)) {
val = false;
} else if (val === 'null') {
val = null;
} else {
val = Number(val);
}
obj[prop] = val;
return obj;
}, {});
/**
* Is stdout a TTY? Colored output is enabled when `true`.
*/
function useColors() {
return 'colors' in exports.inspectOpts ?
Boolean(exports.inspectOpts.colors) :
tty.isatty(process.stderr.fd);
}
/**
* Adds ANSI color escape codes if enabled.
*
* @api public
*/
function formatArgs(args) {
const {namespace: name, useColors} = this;
if (useColors) {
const c = this.color;
const colorCode = '\u001B[3' + (c < 8 ? c : '8;5;' + c);
const prefix = ` ${colorCode};1m${name} \u001B[0m`;
args[0] = prefix + args[0].split('\n').join('\n' + prefix);
args.push(colorCode + 'm+' + module.exports.humanize(this.diff) + '\u001B[0m');
} else {
args[0] = getDate() + name + ' ' + args[0];
}
}
function getDate() {
if (exports.inspectOpts.hideDate) {
return '';
}
return new Date().toISOString() + ' ';
}
/**
* Invokes `util.formatWithOptions()` with the specified arguments and writes to stderr.
*/
function log(...args) {
return process.stderr.write(util.formatWithOptions(exports.inspectOpts, ...args) + '\n');
}
/**
* Save `namespaces`.
*
* @param {String} namespaces
* @api private
*/
function save(namespaces) {
if (namespaces) {
process.env.DEBUG = namespaces;
} else {
// If you set a process.env field to null or undefined, it gets cast to the
// string 'null' or 'undefined'. Just delete instead.
delete process.env.DEBUG;
}
}
/**
* Load `namespaces`.
*
* @return {String} returns the previously persisted debug modes
* @api private
*/
function load() {
return process.env.DEBUG;
}
/**
* Init logic for `debug` instances.
*
* Create a new `inspectOpts` object in case `useColors` is set
* differently for a particular `debug` instance.
*/
function init(debug) {
debug.inspectOpts = {};
const keys = Object.keys(exports.inspectOpts);
for (let i = 0; i < keys.length; i++) {
debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]];
}
}
module.exports = require('./common')(exports);
const {formatters} = module.exports;
/**
* Map %o to `util.inspect()`, all on a single line.
*/
formatters.o = function (v) {
this.inspectOpts.colors = this.useColors;
return util.inspect(v, this.inspectOpts)
.split('\n')
.map(str => str.trim())
.join(' ');
};
/**
* Map %O to `util.inspect()`, allowing multiple lines if needed.
*/
formatters.O = function (v) {
this.inspectOpts.colors = this.useColors;
return util.inspect(v, this.inspectOpts);
};
+162
View File
@@ -0,0 +1,162 @@
/**
* Helpers.
*/
var s = 1000;
var m = s * 60;
var h = m * 60;
var d = h * 24;
var w = d * 7;
var y = d * 365.25;
/**
* Parse or format the given `val`.
*
* Options:
*
* - `long` verbose formatting [false]
*
* @param {String|Number} val
* @param {Object} [options]
* @throws {Error} throw an error if val is not a non-empty string or a number
* @return {String|Number}
* @api public
*/
module.exports = function (val, options) {
options = options || {};
var type = typeof val;
if (type === 'string' && val.length > 0) {
return parse(val);
} else if (type === 'number' && isFinite(val)) {
return options.long ? fmtLong(val) : fmtShort(val);
}
throw new Error(
'val is not a non-empty string or a valid number. val=' +
JSON.stringify(val)
);
};
/**
* Parse the given `str` and return milliseconds.
*
* @param {String} str
* @return {Number}
* @api private
*/
function parse(str) {
str = String(str);
if (str.length > 100) {
return;
}
var match = /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec(
str
);
if (!match) {
return;
}
var n = parseFloat(match[1]);
var type = (match[2] || 'ms').toLowerCase();
switch (type) {
case 'years':
case 'year':
case 'yrs':
case 'yr':
case 'y':
return n * y;
case 'weeks':
case 'week':
case 'w':
return n * w;
case 'days':
case 'day':
case 'd':
return n * d;
case 'hours':
case 'hour':
case 'hrs':
case 'hr':
case 'h':
return n * h;
case 'minutes':
case 'minute':
case 'mins':
case 'min':
case 'm':
return n * m;
case 'seconds':
case 'second':
case 'secs':
case 'sec':
case 's':
return n * s;
case 'milliseconds':
case 'millisecond':
case 'msecs':
case 'msec':
case 'ms':
return n;
default:
return undefined;
}
}
/**
* Short format for `ms`.
*
* @param {Number} ms
* @return {String}
* @api private
*/
function fmtShort(ms) {
var msAbs = Math.abs(ms);
if (msAbs >= d) {
return Math.round(ms / d) + 'd';
}
if (msAbs >= h) {
return Math.round(ms / h) + 'h';
}
if (msAbs >= m) {
return Math.round(ms / m) + 'm';
}
if (msAbs >= s) {
return Math.round(ms / s) + 's';
}
return ms + 'ms';
}
/**
* Long format for `ms`.
*
* @param {Number} ms
* @return {String}
* @api private
*/
function fmtLong(ms) {
var msAbs = Math.abs(ms);
if (msAbs >= d) {
return plural(ms, msAbs, d, 'day');
}
if (msAbs >= h) {
return plural(ms, msAbs, h, 'hour');
}
if (msAbs >= m) {
return plural(ms, msAbs, m, 'minute');
}
if (msAbs >= s) {
return plural(ms, msAbs, s, 'second');
}
return ms + ' ms';
}
/**
* Pluralization helper.
*/
function plural(ms, msAbs, n, name) {
var isPlural = msAbs >= n * 1.5;
return Math.round(ms / n) + ' ' + name + (isPlural ? 's' : '');
}
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2020 Vercel, Inc.
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.
+38
View File
@@ -0,0 +1,38 @@
{
"name": "ms",
"version": "2.1.3",
"description": "Tiny millisecond conversion utility",
"repository": "vercel/ms",
"main": "./index",
"files": [
"index.js"
],
"scripts": {
"precommit": "lint-staged",
"lint": "eslint lib/* bin/*",
"test": "mocha tests.js"
},
"eslintConfig": {
"extends": "eslint:recommended",
"env": {
"node": true,
"es6": true
}
},
"lint-staged": {
"*.js": [
"npm run lint",
"prettier --single-quote --write",
"git add"
]
},
"license": "MIT",
"devDependencies": {
"eslint": "4.18.2",
"expect.js": "0.3.1",
"husky": "0.14.3",
"lint-staged": "5.0.0",
"mocha": "4.0.1",
"prettier": "2.0.5"
}
}
+59
View File
@@ -0,0 +1,59 @@
# ms
![CI](https://github.com/vercel/ms/workflows/CI/badge.svg)
Use this package to easily convert various time formats to milliseconds.
## Examples
```js
ms('2 days') // 172800000
ms('1d') // 86400000
ms('10h') // 36000000
ms('2.5 hrs') // 9000000
ms('2h') // 7200000
ms('1m') // 60000
ms('5s') // 5000
ms('1y') // 31557600000
ms('100') // 100
ms('-3 days') // -259200000
ms('-1h') // -3600000
ms('-200') // -200
```
### Convert from Milliseconds
```js
ms(60000) // "1m"
ms(2 * 60000) // "2m"
ms(-3 * 60000) // "-3m"
ms(ms('10 hours')) // "10h"
```
### Time Format Written-Out
```js
ms(60000, { long: true }) // "1 minute"
ms(2 * 60000, { long: true }) // "2 minutes"
ms(-3 * 60000, { long: true }) // "-3 minutes"
ms(ms('10 hours'), { long: true }) // "10 hours"
```
## Features
- Works both in [Node.js](https://nodejs.org) and in the browser
- If a number is supplied to `ms`, a string with a unit is returned
- If a string that contains the number is supplied, it returns it as a number (e.g.: it returns `100` for `'100'`)
- If you pass a string with a number and a valid unit, the number of equivalent milliseconds is returned
## Related Packages
- [ms.macro](https://github.com/knpwrs/ms.macro) - Run `ms` as a macro at build-time.
## Caught a Bug?
1. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device
2. Link the package to the global module directory: `npm link`
3. Within the module you want to test your local development instance of ms, just link it to the dependencies: `npm link ms`. Instead of the default one from npm, Node.js will now use your clone of ms!
As always, you can run the tests using: `npm test`
+43
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"
]
}