123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542 |
- '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)
- }
|