Initial commit: GamePortrait 竖屏版 - 深色豪华主题

This commit is contained in:
li
2026-01-16 17:47:53 +08:00
commit 40976e4b45
26611 changed files with 2843638 additions and 0 deletions
@@ -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)
}