Update checked-in dependencies

This commit is contained in:
github-actions[bot] 2021-07-27 16:54:30 +00:00
parent 35f1961385
commit 17223bdff7
20 changed files with 1756 additions and 646 deletions

View file

@ -13,6 +13,20 @@ const globalEmitter = require('./global_emitter')
const Socket = require('./socket')
const { playbackInterceptor } = require('./playback_interceptor')
function socketOnClose(req) {
debug('socket close')
if (!req.res && !req.socket._hadError) {
// If we don't have a response then we know that the socket
// ended prematurely and we need to emit an error on the request.
req.socket._hadError = true
const err = new Error('socket hang up')
err.code = 'ECONNRESET'
req.emit('error', err)
}
req.emit('close')
}
/**
* Given a group of interceptors, appropriately route an outgoing request.
* Identify which interceptor ought to respond, if any, then delegate to
@ -39,16 +53,24 @@ class InterceptedRequestRouter {
}
this.response = new IncomingMessage(this.socket)
this.playbackStarted = false
this.requestBodyBuffers = []
this.playbackStarted = false
// For parity with Node, it's important the socket event is emitted before we begin playback.
// This flag is set when playback is triggered if we haven't yet gotten the
// socket event to indicate that playback should start as soon as it comes in.
this.readyToStartPlaybackOnSocketEvent = false
this.attachToReq()
// Emit a fake socket event on the next tick to mimic what would happen on a real request.
// Some clients listen for a 'socket' event to be emitted before calling end(),
// which causes Nock to hang.
process.nextTick(() => this.connectSocket())
}
attachToReq() {
const { req, response, socket, options } = this
response.req = req
const { req, options } = this
for (const [name, val] of Object.entries(options.headers)) {
req.setHeader(name.toLowerCase(), val)
@ -65,18 +87,9 @@ class InterceptedRequestRouter {
req.path = options.path
req.method = options.method
// ClientRequest.connection is an alias for ClientRequest.socket
// https://nodejs.org/api/http.html#http_request_socket
// https://github.com/nodejs/node/blob/b0f75818f39ed4e6bd80eb7c4010c1daf5823ef7/lib/_http_client.js#L640-L641
// The same Socket is shared between the request and response to mimic native behavior.
req.socket = req.connection = socket
propagate(['error', 'timeout'], req.socket, req)
req.write = (...args) => this.handleWrite(...args)
req.end = (...args) => this.handleEnd(...args)
req.flushHeaders = (...args) => this.handleFlushHeaders(...args)
req.abort = (...args) => this.handleAbort(...args)
// https://github.com/nock/nock/issues/256
if (options.headers.expect === '100-continue') {
@ -85,53 +98,79 @@ class InterceptedRequestRouter {
req.emit('continue')
})
}
// Emit a fake socket event on the next tick to mimic what would happen on a real request.
// Some clients listen for a 'socket' event to be emitted before calling end(),
// which causes nock to hang.
process.nextTick(() => {
req.emit('socket', socket)
// https://nodejs.org/api/net.html#net_event_connect
socket.emit('connect')
// https://nodejs.org/api/tls.html#tls_event_secureconnect
if (socket.authorized) {
socket.emit('secureConnect')
}
})
}
emitError(error) {
const { req } = this
process.nextTick(() => {
req.emit('error', error)
})
}
connectSocket() {
const { req, socket } = this
handleWrite(buffer, encoding, callback) {
debug('write', arguments)
const { req } = this
if (!req.aborted) {
if (buffer) {
if (!Buffer.isBuffer(buffer)) {
buffer = Buffer.from(buffer, encoding)
}
this.requestBodyBuffers.push(buffer)
}
// can't use instanceof Function because some test runners
// run tests in vm.runInNewContext where Function is not same
// as that in the current context
// https://github.com/nock/nock/pull/1754#issuecomment-571531407
if (typeof callback === 'function') {
callback()
}
} else {
this.emitError(new Error('Request aborted'))
if (common.isRequestDestroyed(req)) {
return
}
common.setImmediate(function() {
// ClientRequest.connection is an alias for ClientRequest.socket
// https://nodejs.org/api/http.html#http_request_socket
// https://github.com/nodejs/node/blob/b0f75818f39ed4e6bd80eb7c4010c1daf5823ef7/lib/_http_client.js#L640-L641
// The same Socket is shared between the request and response to mimic native behavior.
req.socket = req.connection = socket
propagate(['error', 'timeout'], socket, req)
socket.on('close', () => socketOnClose(req))
socket.connecting = false
req.emit('socket', socket)
// https://nodejs.org/api/net.html#net_event_connect
socket.emit('connect')
// https://nodejs.org/api/tls.html#tls_event_secureconnect
if (socket.authorized) {
socket.emit('secureConnect')
}
if (this.readyToStartPlaybackOnSocketEvent) {
this.maybeStartPlayback()
}
}
// from docs: When write function is called with empty string or buffer, it does nothing and waits for more input.
// However, actually implementation checks the state of finished and aborted before checking if the first arg is empty.
handleWrite(buffer, encoding, callback) {
debug('request write')
const { req } = this
if (req.finished) {
const err = new Error('write after end')
err.code = 'ERR_STREAM_WRITE_AFTER_END'
process.nextTick(() => req.emit('error', err))
// It seems odd to return `true` here, not sure why you'd want to have
// the stream potentially written to more, but it's what Node does.
// https://github.com/nodejs/node/blob/a9270dcbeba4316b1e179b77ecb6c46af5aa8c20/lib/_http_outgoing.js#L662-L665
return true
}
if (req.socket && req.socket.destroyed) {
return false
}
if (!buffer || buffer.length === 0) {
return true
}
if (!Buffer.isBuffer(buffer)) {
buffer = Buffer.from(buffer, encoding)
}
this.requestBodyBuffers.push(buffer)
// can't use instanceof Function because some test runners
// run tests in vm.runInNewContext where Function is not same
// as that in the current context
// https://github.com/nock/nock/pull/1754#issuecomment-571531407
if (typeof callback === 'function') {
callback()
}
common.setImmediate(function () {
req.emit('drain')
})
@ -139,10 +178,10 @@ class InterceptedRequestRouter {
}
handleEnd(chunk, encoding, callback) {
debug('req.end')
debug('request end')
const { req } = this
// handle the different overloaded param signatures
// handle the different overloaded arg signatures
if (typeof chunk === 'function') {
callback = chunk
chunk = null
@ -155,51 +194,18 @@ class InterceptedRequestRouter {
req.once('finish', callback)
}
if (!req.aborted && !this.playbackStarted) {
if (chunk) {
req.write(chunk, encoding)
this.startPlayback()
}
if (req.aborted) {
this.emitError(new Error('Request aborted'))
}
req.finished = true
this.maybeStartPlayback()
return req
}
handleFlushHeaders() {
debug('req.flushHeaders')
const { req } = this
if (!req.aborted && !this.playbackStarted) {
this.startPlayback()
}
if (req.aborted) {
this.emitError(new Error('Request aborted'))
}
}
handleAbort() {
debug('req.abort')
const { req, response, socket } = this
if (req.aborted) {
return
}
req.aborted = Date.now()
if (!this.playbackStarted) {
this.startPlayback()
}
const err = new Error()
err.code = 'aborted'
response.emit('close', err)
socket.destroy()
req.emit('abort')
const connResetError = new Error('socket hang up')
connResetError.code = 'ECONNRESET'
this.emitError(connResetError)
debug('request flushHeaders')
this.maybeStartPlayback()
}
/**
@ -233,6 +239,21 @@ class InterceptedRequestRouter {
}
}
maybeStartPlayback() {
const { req, socket, playbackStarted } = this
// In order to get the events in the right order we need to delay playback
// if we get here before the `socket` event is emitted.
if (socket.connecting) {
this.readyToStartPlaybackOnSocketEvent = true
return
}
if (!common.isRequestDestroyed(req) && !playbackStarted) {
this.startPlayback()
}
}
startPlayback() {
debug('ending')
this.playbackStarted = true
@ -270,11 +291,14 @@ class InterceptedRequestRouter {
)
if (matchedInterceptor) {
debug('interceptor identified, starting mocking')
matchedInterceptor.scope.logger(
'interceptor identified, starting mocking'
)
matchedInterceptor.markConsumed()
// wait to emit the finish event until we know for sure an Interceptor is going to playback.
// otherwise an unmocked request might emit finish twice.
req.finished = true
req.emit('finish')
playbackInterceptor({
@ -304,14 +328,11 @@ class InterceptedRequestRouter {
// We send the raw buffer as we received it, not as we interpreted it.
newReq.end(requestBodyBuffer)
} else {
const err = new Error(
`Nock: No match for request ${common.stringifyRequest(
options,
requestBodyString
)}`
)
const reqStr = common.stringifyRequest(options, requestBodyString)
const err = new Error(`Nock: No match for request ${reqStr}`)
err.code = 'ERR_NOCK_NO_MATCH'
err.statusCode = err.status = 404
this.emitError(err)
req.destroy(err)
}
}
}