undici
Version:
An HTTP/1.1 client, written from scratch for Node.js
1,065 lines (902 loc) • 29.2 kB
JavaScript
'use strict'
const assert = require('node:assert')
const util = require('../core/util.js')
const timers = require('../util/timers.js')
const {
HeadersTimeoutError,
HeadersOverflowError,
SocketError,
InformationalError,
BodyTimeoutError,
HTTPParserError,
ResponseContentLengthMismatchError,
ResponseExceededMaxSizeError
} = require('../core/errors.js')
const {
kUrl,
kReset,
kClient,
kParser,
kBlocking,
kRunning,
kWriting,
kQueue,
kKeepAliveDefaultTimeout,
kRunningIdx,
kError,
kPipelining,
kSocket,
kKeepAliveTimeoutValue,
kKeepAliveMaxTimeout,
kKeepAliveTimeoutThreshold,
kBodyTimeout,
kMaxResponseSize,
kResume,
kHTTPContext,
kMaxHeadersSize
} = require('../core/symbols.js')
const FastBuffer = Buffer[Symbol.species]
const removeAllListeners = util.removeAllListeners
const kPausedByHandler = Symbol('kPausedByHandler')
const BUFFER_PAGE_SIZE = 4096
const INITIAL_BUFFER_SIZE = 128 * 1024
const EMPTY_BUF = Buffer.alloc(0)
const BODY_KIND_CONTENT_LENGTH = 0
const EVENT_RANGE_SIZE = 9
const EVENT_HEADERS_SIZE = 19
let miloPackage
// We disable wasm SIMD on ppc64 as it seems to be broken on Power 9 architectures.
let useWasmSIMD = process.arch !== 'ppc64'
// The Env Variable UNDICI_NO_WASM_SIMD allows explicitly overriding the default behavior.
if (process.env.UNDICI_NO_WASM_SIMD === '1') {
useWasmSIMD = false
} else if (process.env.UNDICI_NO_WASM_SIMD === '0') {
useWasmSIMD = true
}
const USE_NATIVE_TIMER = 0
const USE_FAST_TIMER = 1
const TIMEOUT_HEADERS = 2 | USE_FAST_TIMER
const TIMEOUT_BODY = 4 | USE_FAST_TIMER
const TIMEOUT_KEEP_ALIVE = 8 | USE_NATIVE_TIMER
let miloEnabled =
process.env.UNDICI_USE_MILO === '1' || process.env.UNDICI_USE_MILO === 'true'
let miloInstance = null
let currentBuffer = null
let currentBufferSize = 0
let currentBufferPtr = null
let currentBufferRef = null
function onParserTimeout (parserWeakRef) {
const parser = parserWeakRef.deref ? parserWeakRef.deref() : parserWeakRef
if (!parser) {
return
}
const { socket, timeoutType, client, paused } = parser
if (timeoutType === TIMEOUT_HEADERS) {
if (!socket[kWriting] || socket.writableNeedDrain || client[kRunning] > 1) {
assert(!paused, 'cannot be paused while waiting for headers')
util.destroy(socket, new HeadersTimeoutError())
}
} else if (timeoutType === TIMEOUT_BODY) {
if (!paused) {
util.destroy(socket, new BodyTimeoutError())
}
} else if (timeoutType === TIMEOUT_KEEP_ALIVE) {
assert(client[kRunning] === 0 && client[kKeepAliveTimeoutValue])
util.destroy(socket, new InformationalError('socket idle timeout'))
}
}
function getMilo () {
if (miloInstance) {
return miloInstance
}
// Milo modules compile WASM on load; defer loading until the parser is needed.
if (useWasmSIMD) {
try {
miloPackage = require('../milo/simd.js')
} catch {}
}
miloPackage = miloPackage || require('../milo/no-simd.js')
miloInstance = miloPackage.setup()
return miloInstance
}
class H1Parser {
constructor (client, socket) {
this.milo = getMilo()
this.client = client
this.socket = socket
this.parser = this.milo.create()
this.events = new FastBuffer(
this.milo.memory.buffer,
this.parser + this.milo.ParserFields.EVENTS,
4
).readUInt32LE(0)
this.milo.setShouldAutodetect(this.parser, false)
this.milo.setIsRequest(this.parser, false)
this.milo.setShouldManageUnconsumed(this.parser, false)
this.milo.setShouldSuspendAfterHeaders(this.parser, true)
this.milo.setMaxHeaderLength(this.parser, client[kMaxHeadersSize])
this.milo.setActiveEvents(
this.parser,
this.milo.EVENT_ACTIVE_ON_ERROR |
this.milo.EVENT_ACTIVE_ON_MESSAGE_COMPLETE |
this.milo.EVENT_ACTIVE_ON_REASON |
this.milo.EVENT_ACTIVE_ON_HEADER_NAME |
this.milo.EVENT_ACTIVE_ON_HEADER_VALUE |
this.milo.EVENT_ACTIVE_ON_HEADERS |
this.milo.EVENT_ACTIVE_ON_DATA |
this.milo.EVENT_ACTIVE_ON_TRAILER_NAME |
this.milo.EVENT_ACTIVE_ON_TRAILER_VALUE
)
this.timeout = null
this.timeoutWeakRef = new WeakRef(this)
this.timeoutValue = null
this.timeoutType = null
this.statusCode = 0
this.statusText = ''
this.upgrade = false
this.responseStarted = false
this.pausedByParser = false
this.pauseStateIsStart = false
this.headers = []
this.trailers = []
this.headersSize = 0
this.trailersSize = 0
this.headersMaxSize = client[kMaxHeadersSize]
this.shouldKeepAlive = false
this.paused = false
this.errored = false
this.waitingForMoreData = 0
this.resume = this.resume.bind(this)
this.bytesRead = 0
this.keepAlive = ''
this.connection = ''
this.contentLength = -1
this.maxResponseSize = client[kMaxResponseSize]
this.directBodyRemaining = 0
this.directBodyCompletePending = false
this.headersSuspended = false
this.skipBody = false
this[kPausedByHandler] = false
if (
INITIAL_BUFFER_SIZE > currentBufferSize ||
currentBuffer.buffer !== this.milo.memory.buffer
) {
this.ensureBuffers(INITIAL_BUFFER_SIZE)
} else {
this.updateBuffers()
}
}
setTimeout (delay, type) {
if (
delay !== this.timeoutValue ||
(type & USE_FAST_TIMER) ^ (this.timeoutType & USE_FAST_TIMER)
) {
if (this.timeout) {
timers.clearTimeout(this.timeout)
this.timeout = null
}
if (delay) {
if (type & USE_FAST_TIMER) {
this.timeout = timers.setFastTimeout(
onParserTimeout,
delay,
this.timeoutWeakRef
)
} else {
this.timeout = setTimeout(
onParserTimeout,
delay,
this.timeoutWeakRef
)
this.timeout?.unref()
}
}
this.timeoutValue = delay
} else if (this.timeout?.refresh) {
this.timeout.refresh()
}
this.timeoutType = type
}
ensureBuffers (size) {
const { milo } = this
if (currentBufferPtr != null) {
milo.dealloc(currentBufferPtr, currentBufferSize)
}
// Reallocate buffers
currentBufferSize = Math.ceil(size / BUFFER_PAGE_SIZE) * BUFFER_PAGE_SIZE
currentBufferPtr = milo.alloc(currentBufferSize)
currentBuffer = new FastBuffer(
milo.memory.buffer,
currentBufferPtr,
currentBufferSize
)
this.updateBuffers()
}
updateBuffers () {
this.parserFields = new FastBuffer(this.milo.memory.buffer, this.parser)
this.parserEvents = new FastBuffer(
this.milo.memory.buffer,
this.events,
64 * 1024
)
}
resume () {
if (this.socket.destroyed || !this.paused) {
return
}
this.milo.resume(this.parser)
assert(this.timeoutType === TIMEOUT_BODY)
if (this.timeout?.refresh) {
this.timeout.refresh()
}
this.paused = false
if (this.directBodyCompletePending) {
try {
this.completeDirectBody()
} catch (err) {
util.destroy(this.socket, err)
return
}
if (this.paused) {
return
}
}
this.readMore()
}
readMore () {
if (this.paused || !this.parser) {
return
}
while (!this.paused && this.parser) {
if (
this.waitingForMoreData &&
this.socket.readableLength <= this.waitingForMoreData
) {
break
}
this.waitingForMoreData = 0
const chunk = this.socket.read()
if (chunk === null) {
break
}
if (this.execute(chunk) === false) {
break
}
}
}
execute (data) {
const available = data.length
assert(!this.paused)
const { socket, milo } = this
try {
if (this.directBodyRemaining > 0) {
return this.consumeDirectBody(data)
}
if (currentBuffer.buffer !== milo.memory.buffer) {
currentBuffer = new FastBuffer(
milo.memory.buffer,
currentBufferPtr,
currentBufferSize
)
this.updateBuffers()
} else if (this.parserEvents.buffer !== milo.memory.buffer) {
this.updateBuffers()
}
let offset = 0
let parseLen = available
const headersEnd = this.statusCode === 0 ? data.indexOf('\r\n\r\n') : -1
if (headersEnd !== -1) {
parseLen = headersEnd + 4
}
for (;;) {
if (parseLen > currentBufferSize) {
this.ensureBuffers(parseLen)
}
const parseData = data.subarray(offset, offset + parseLen)
currentBuffer.set(parseData)
currentBufferRef = parseData
const consumed = milo.parse(this.parser, currentBufferPtr, parseLen)
this.drainEvents()
const consumedTotal = offset + consumed
if (
this.errored ||
(consumed === 0 &&
this.parserFields[milo.ParserFields.ERROR_CODE] !== milo.ERROR_NONE)
) {
throw this.createError(data.subarray(consumedTotal))
} else if (this.upgrade) {
this.onUpgrade(data.subarray(consumedTotal))
return false
} else if (this.directBodyCompletePending) {
this.completeDirectBody()
if (this.paused) {
return false
}
} else if (this.pausedByParser) {
this.pausedByParser = false
this.headersSuspended = false
if (this[kPausedByHandler] && this.pauseStateIsStart) {
this.pauseStateIsStart = false
milo.resume(this.parser)
this[kPausedByHandler] = false
return false
}
this.pauseStateIsStart = false
this.paused = true
socket.unshift(data.subarray(consumedTotal))
return false
}
if (this.directBodyRemaining > 0) {
return this.consumeDirectBody(data.subarray(consumedTotal))
}
if (consumed < parseLen) {
const remaining = data.subarray(consumedTotal)
socket.unshift(remaining)
this.waitingForMoreData = consumed === 0 ? remaining.length : 0
return false
}
offset = consumedTotal
if (offset >= available) {
if (this.headersSuspended) {
this.headersSuspended = false
parseLen = 0
continue
}
return true
}
this.headersSuspended = false
parseLen = available - offset
}
} catch (err) {
util.destroy(socket, err)
return false
} finally {
currentBufferRef = null
}
}
consumeDirectBody (data) {
const { socket, client, maxResponseSize } = this
const available = data.length
const len = Math.min(available, this.directBodyRemaining)
if (this.timeout?.refresh) {
this.timeout.refresh()
}
if (len > 0) {
const request = client[kQueue][client[kRunningIdx]]
assert(request)
assert(this.timeoutType === TIMEOUT_BODY)
assert(this.statusCode >= 200)
if (maxResponseSize > -1 && this.bytesRead + len > maxResponseSize) {
throw new ResponseExceededMaxSizeError()
}
this.bytesRead += len
this.directBodyRemaining -= len
if (request.onResponseData(data.subarray(0, len)) === false) {
this.directBodyCompletePending = this.directBodyRemaining === 0
this.pauseParser()
this[kPausedByHandler] = true
this.pausedByParser = false
this.paused = true
if (len < available) {
socket.unshift(data.subarray(len))
}
return false
}
}
if (this.directBodyRemaining === 0) {
this.completeDirectBody()
const remaining = data.subarray(len)
if (
this.errored ||
this.parserFields[this.milo.ParserFields.ERROR_CODE] !==
this.milo.ERROR_NONE
) {
throw this.createError(remaining)
} else if (this.pausedByParser) {
this.pausedByParser = false
this.pauseStateIsStart = false
this.paused = true
if (remaining.length > 0) {
socket.unshift(remaining)
}
return false
}
if (remaining.length > 0) {
socket.unshift(remaining)
return false
}
}
return true
}
completeDirectBody () {
const { milo } = this
if (currentBuffer.buffer !== milo.memory.buffer) {
currentBuffer = new FastBuffer(
milo.memory.buffer,
currentBufferPtr,
currentBufferSize
)
this.updateBuffers()
} else if (this.parserEvents.buffer !== milo.memory.buffer) {
this.updateBuffers()
}
this.directBodyCompletePending = false
currentBufferRef = EMPTY_BUF
milo.complete(this.parser)
this.drainEvents()
}
setShouldSkipBody (value) {
if (this.skipBody !== value) {
this.skipBody = value
this.milo.setShouldSkipBody(this.parser, value)
}
}
finish () {
if (!this.parser) {
return null
}
if (this.directBodyRemaining > 0) {
return new ResponseContentLengthMismatchError()
}
if (this.parserEvents.buffer !== this.milo.memory.buffer) {
this.updateBuffers()
}
this.milo.finish(this.parser)
this.drainEvents()
if (!this.errored) {
return null
}
return this.createError(EMPTY_BUF)
}
createError (data) {
const { contentLength, bytesRead } = this
if (contentLength !== -1 && bytesRead !== contentLength) {
return new ResponseContentLengthMismatchError()
}
const errorCode = this.parserFields[this.milo.ParserFields.ERROR_CODE]
const errorName = this.milo.Errors[errorCode]
const errorDescription =
errorName === 'UNEXPECTED_EOF'
? 'Invalid EOF state'
: this.milo.getErrorDescription(this.parser)
const reason =
errorName === 'UNEXPECTED_EOF'
? errorDescription
: errorDescription
? `[ERROR_${errorName}] ${errorDescription}`
: `ERROR_${errorName}`
return new HTTPParserError(
`Response does not match the HTTP/1.1 protocol (${reason})`,
errorName,
data
)
}
destroy () {
if (this.parser) {
this.milo.destroy(this.parser)
this.parser = null
}
this.timeout && timers.clearTimeout(this.timeout)
this.timeout = null
this.timeoutValue = null
this.timeoutType = null
this.paused = false
this.responseStarted = false
this.pausedByParser = false
this.pauseStateIsStart = false
this.connection = ''
this.skipBody = false
}
pauseParser (stateIsStart = false) {
this.milo.pause(this.parser)
this.pausedByParser = true
this.pauseStateIsStart = stateIsStart
}
drainEvents () {
const { socket, client, milo } = this
let cursor = 0
const firstEvent = this.parserEvents[0]
if (
firstEvent !== milo.EVENT_END &&
firstEvent !== milo.EVENT_ERROR &&
!this.responseStarted
) {
this.responseStarted = true
if (socket.destroyed) {
return this.fail(this.milo.ERROR_CALLBACK_ERROR, 'Other side closed')
}
const request = client[kQueue][client[kRunningIdx]]
if (!request) {
return this.fail(this.milo.ERROR_CALLBACK_ERROR, 'Missing request')
}
request.onResponseStarted()
}
for (;;) {
const event = this.parserEvents[cursor]
let eventLen = EVENT_RANGE_SIZE
switch (event) {
case milo.EVENT_END:
return
case milo.EVENT_ERROR:
this.errored = true
return
case milo.EVENT_MESSAGE_COMPLETE: {
const {
client,
socket,
statusCode,
upgrade,
trailers,
contentLength,
bytesRead,
shouldKeepAlive
} = this
this.responseStarted = false
if (socket.destroyed && (!statusCode || shouldKeepAlive)) {
return this.fail(milo.ERROR_CALLBACK_ERROR, 'Other side closed')
}
if (upgrade) {
this.pauseParser()
return
}
assert(statusCode >= 100)
const request = client[kQueue][client[kRunningIdx]]
assert(request)
this.statusCode = 0
this.statusText = ''
this.bytesRead = 0
this.contentLength = -1
this.directBodyRemaining = 0
this.directBodyCompletePending = false
this.headersSuspended = false
this.skipBody = false
this.keepAlive = ''
this.connection = ''
this.trailers = []
this.trailersSize = 0
if (statusCode < 200) {
break
}
if (
request.method !== 'HEAD' &&
contentLength !== -1 &&
bytesRead !== contentLength
) {
util.destroy(socket, new ResponseContentLengthMismatchError())
this.pauseParser()
break
}
request.onResponseEnd(trailers)
client[kQueue][client[kRunningIdx]++] = null
if (socket[kWriting]) {
assert(client[kRunning] === 0)
util.destroy(socket, new InformationalError('reset'))
this.pauseParser(true)
} else if (!shouldKeepAlive) {
util.destroy(socket, new InformationalError('reset'))
this.pauseParser(true)
} else if (socket[kReset] && client[kRunning] === 0) {
util.destroy(socket, new InformationalError('reset'))
this.pauseParser(true)
} else if (client[kPipelining] == null || client[kPipelining] === 1) {
setImmediate(client[kResume])
} else {
client[kResume]()
}
break
}
case milo.EVENT_REASON: {
const at = this.parserEvents.readUInt32LE(cursor + 1)
const len = this.parserEvents.readUInt32LE(cursor + 5)
this.statusText = new FastBuffer(
currentBufferRef.buffer,
currentBufferRef.byteOffset + at,
len
).toString()
break
}
case milo.EVENT_HEADER_NAME: {
const at = this.parserEvents.readUInt32LE(cursor + 1)
const len = this.parserEvents.readUInt32LE(cursor + 5)
const data =
len > 0
? new FastBuffer(
currentBufferRef.buffer,
currentBufferRef.byteOffset + at,
len
)
: EMPTY_BUF
this.headers.push(data)
this.headersSize += len
if (this.headersSize >= this.headersMaxSize) {
util.destroy(this.socket, new HeadersOverflowError())
return this.fail(milo.ERROR_CALLBACK_ERROR, 'Headers overflow')
}
break
}
case milo.EVENT_HEADER_VALUE: {
const at = this.parserEvents.readUInt32LE(cursor + 1)
const len = this.parserEvents.readUInt32LE(cursor + 5)
const data =
len > 0
? new FastBuffer(
currentBufferRef.buffer,
currentBufferRef.byteOffset + at,
len
)
: EMPTY_BUF
const key = this.headers[this.headers.length - 1]
this.headers.push(data)
if (key.length === 10) {
const headerName = util.bufferToLowerCasedHeaderName(key)
if (headerName === 'keep-alive') {
this.keepAlive += (this.keepAlive.length === 0 ? '' : ',') + data.toString()
} else if (headerName === 'connection') {
this.connection += (this.connection.length === 0 ? '' : ',') + data.toString()
}
}
this.headersSize += len
if (this.headersSize >= this.headersMaxSize) {
util.destroy(this.socket, new HeadersOverflowError())
return this.fail(milo.ERROR_CALLBACK_ERROR, 'Headers overflow')
}
break
}
case milo.EVENT_HEADERS: {
eventLen = EVENT_HEADERS_SIZE
const statusCode = this.parserEvents.readUInt16LE(cursor + 5)
const shouldKeepAlive = this.parserEvents[cursor + 7] !== 0
const shouldUpgrade = this.parserEvents[cursor + 8] !== 0
const hasTrailers = this.parserEvents[cursor + 9] !== 0
const bodyKind = this.parserEvents[cursor + 10]
const contentLength =
this.parserEvents.readUInt32LE(cursor + 11) +
this.parserEvents.readUInt32LE(cursor + 15) * 0x100000000
const { socket, client, headers, statusText } = this
if (socket.destroyed) {
return this.fail(milo.ERROR_CALLBACK_ERROR, 'Other side closed')
}
const request = client[kQueue][client[kRunningIdx]]
if (!request) {
return this.fail(milo.ERROR_CALLBACK_ERROR, 'Missing request')
}
assert(!this.upgrade)
assert(this.statusCode < 200)
if (statusCode === 100) {
util.destroy(
socket,
new SocketError('bad response', util.getSocketInfo(socket))
)
return this.fail(milo.ERROR_CALLBACK_ERROR, 'Bad response')
}
if (shouldUpgrade && !request.upgrade) {
util.destroy(
socket,
new SocketError('bad upgrade', util.getSocketInfo(socket))
)
return this.fail(milo.ERROR_CALLBACK_ERROR, 'Bad upgrade')
}
assert(this.timeoutType === TIMEOUT_HEADERS)
this.statusCode = statusCode
this.shouldKeepAlive =
shouldKeepAlive ||
(request.method === 'HEAD' &&
!socket[kReset] &&
/(?:^|,)[\t ]*keep-alive[\t ]*(?:,|$)/i.test(this.connection) &&
!/(?:^|,)[\t ]*close[\t ]*(?:,|$)/i.test(this.connection))
this.contentLength =
bodyKind === BODY_KIND_CONTENT_LENGTH ? contentLength : -1
if (this.statusCode >= 200) {
const bodyTimeout =
request.bodyTimeout != null
? request.bodyTimeout
: client[kBodyTimeout]
this.setTimeout(bodyTimeout, TIMEOUT_BODY)
} else if (this.timeout?.refresh) {
this.timeout.refresh()
}
if (request.method === 'CONNECT') {
assert(client[kRunning] === 1)
milo.setIsConnect(this.parser, true)
this.upgrade = true
return
}
if (shouldUpgrade) {
assert(client[kRunning] === 1)
this.upgrade = true
this.pauseParser()
return
}
this.headers = []
this.headersSize = 0
if (this.shouldKeepAlive && client[kPipelining]) {
const keepAliveTimeout = this.keepAlive
? util.parseKeepAliveTimeout(this.keepAlive)
: null
if (keepAliveTimeout != null) {
const timeout = Math.min(
keepAliveTimeout - client[kKeepAliveTimeoutThreshold],
client[kKeepAliveMaxTimeout]
)
if (timeout <= 0) {
socket[kReset] = true
} else {
client[kKeepAliveTimeoutValue] = timeout
}
} else {
client[kKeepAliveTimeoutValue] = client[kKeepAliveDefaultTimeout]
}
} else {
socket[kReset] = true
}
const pause =
request.onResponseStart(
statusCode,
headers,
this.resume,
statusText
) === false
if (request.aborted) {
return this.fail(milo.ERROR_CALLBACK_ERROR, 'Request aborted')
}
if (request.method === 'HEAD' || statusCode < 200) {
this.setShouldSkipBody(true)
}
this.headersSuspended = true
if (
!pause &&
request.method !== 'HEAD' &&
statusCode >= 200 &&
statusCode !== 204 &&
statusCode !== 205 &&
statusCode !== 304 &&
bodyKind === BODY_KIND_CONTENT_LENGTH &&
contentLength > 0 &&
!hasTrailers
) {
this.directBodyRemaining = contentLength
}
if (socket[kBlocking]) {
socket[kBlocking] = false
client[kResume]()
}
if (pause) {
this.pauseParser()
this[kPausedByHandler] = true
} else if (
request.method === 'HEAD' ||
statusCode < 200 ||
statusCode === 204 ||
statusCode === 205 ||
statusCode === 304 ||
(bodyKind === BODY_KIND_CONTENT_LENGTH && contentLength === 0)
) {
this.directBodyCompletePending = true
}
break
}
case milo.EVENT_DATA: {
const at = this.parserEvents.readUInt32LE(cursor + 1)
const len = this.parserEvents.readUInt32LE(cursor + 5)
const { client, socket, statusCode, maxResponseSize } = this
if (socket.destroyed) {
return this.fail(milo.ERROR_CALLBACK_ERROR, 'Other side closed')
}
const request = client[kQueue][client[kRunningIdx]]
assert(request)
assert(this.timeoutType === TIMEOUT_BODY)
if (this.timeout?.refresh) {
this.timeout.refresh()
}
assert(statusCode >= 200)
if (maxResponseSize > -1 && this.bytesRead + len > maxResponseSize) {
util.destroy(socket, new ResponseExceededMaxSizeError())
return this.fail(
milo.ERROR_CALLBACK_ERROR,
'Response size exceeded'
)
}
this.bytesRead += len
const buffer = currentBufferRef
if (
request.onResponseData(
new FastBuffer(buffer.buffer, buffer.byteOffset + at, len)
) === false
) {
this.pauseParser()
this[kPausedByHandler] = true
}
break
}
case milo.EVENT_TRAILER_NAME: {
const at = this.parserEvents.readUInt32LE(cursor + 1)
const len = this.parserEvents.readUInt32LE(cursor + 5)
const data =
len > 0
? new FastBuffer(
currentBufferRef.buffer,
currentBufferRef.byteOffset + at,
len
)
: EMPTY_BUF
this.trailers.push(data)
this.trailersSize += len
if (this.trailersSize >= this.headersMaxSize) {
util.destroy(this.socket, new HeadersOverflowError())
return this.fail(milo.ERROR_CALLBACK_ERROR, 'Headers overflow')
}
break
}
case milo.EVENT_TRAILER_VALUE: {
const at = this.parserEvents.readUInt32LE(cursor + 1)
const len = this.parserEvents.readUInt32LE(cursor + 5)
const data =
len > 0
? new FastBuffer(
currentBufferRef.buffer,
currentBufferRef.byteOffset + at,
len
)
: EMPTY_BUF
this.trailers.push(data)
this.trailersSize += len
if (this.trailersSize >= this.headersMaxSize) {
util.destroy(this.socket, new HeadersOverflowError())
return this.fail(milo.ERROR_CALLBACK_ERROR, 'Headers overflow')
}
break
}
default:
return
}
cursor += eventLen
}
}
fail (code, message) {
this.milo.fail(this.parser, code, message)
this.errored = true
}
onUpgrade (head) {
const { upgrade, client, socket, headers, statusCode } = this
assert(upgrade)
assert(client[kSocket] === socket)
assert(!socket.destroyed)
assert(!this.paused)
const request = client[kQueue][client[kRunningIdx]]
assert(request)
assert(request.upgrade || request.method === 'CONNECT')
this.statusCode = 0
this.statusText = ''
this.shouldKeepAlive = false
this.headers = []
this.headersSize = 0
socket.unshift(head)
socket[kParser].destroy()
socket[kParser] = null
socket[kClient] = null
socket[kError] = null
removeAllListeners(socket)
client[kSocket] = null
client[kHTTPContext] = null
client[kQueue][client[kRunningIdx]++] = null
client.emit(
'disconnect',
client[kUrl],
[client],
new InformationalError('upgrade')
)
try {
request.onRequestUpgrade(statusCode, headers, socket)
} catch (err) {
util.destroy(socket, err)
}
client[kResume]()
}
}
function setMiloEnabled (value) {
miloEnabled = value
}
function getMiloEnabled () {
return miloEnabled
}
module.exports = { H1Parser, getMiloEnabled, setMiloEnabled }