nock 14 hangs on requests that send Connection: keep-alive
Please avoid duplicates
I searched open issues; #2789 is related but distinct (that one is about replyWithError, see the note at the end).
What happened?
On [email protected], any intercepted request that sends Connection: keep-alive hangs forever. The mocked response is written without Content-Length and without Transfer-Encoding, so it is only terminated by the socket closing — which never happens on a kept-alive connection.
This is fixed in @mswjs/[email protected], which [email protected] already depends on. [email protected] pins ^0.41.0, so the 14 line still hangs.
Reproducible test case
No nock APIs beyond reply() involved — this is plain https.request:
const https = require('node:https')
const nock = require('nock')
nock('https://example.test').get('/ka').reply(200, 'hello')
https
.request(
{ protocol: 'https:', host: 'example.test', path: '/ka', headers: { connection: 'keep-alive' } },
(res) => {
let body = ''
res.on('data', (c) => (body += c))
res.on('end', () => console.log('done', res.statusCode, body))
}
)
.on('error', console.error)
.end()
// connection: keep-alive -> no output, hangs
// connection: close -> "done 200 hello"Results:
| nock | connection: close |
connection: keep-alive |
|---|---|---|
14.0.0-beta.5 |
ok | ok |
14.0.0 |
ok | hangs |
14.0.17 |
ok | hangs |
15.0.0-beta.14 |
ok | ok |
So it arrived with the move to @mswjs/interceptors in 14.0.0 stable and is already resolved on the 15 line.
Root cause
MockHttpSocket.respondWith() builds the response via new ServerResponse(new IncomingMessage(socket)). A bare IncomingMessage reports no HTTP version, so Node's ServerResponse constructor takes the HTTP/1.0 path and sets useChunkedEncodingByDefault = false. The serialized response is then:
HTTP/1.1 200 OK\r\n\r\nhellowith no framing headers at all. MockHttpSocket only pushes null when !this.shouldKeepAlive, so on a keep-alive connection there is no terminator and the client waits for the rest of the message indefinitely.
Adding a content-length to the mock's reply headers works around it:
nock('https://example.test').get('/ka').reply(200, 'hello', { 'content-length': '5' }) // ok@mswjs/[email protected] reworked the socket layer and no longer has this problem.
Ask
Would you consider bumping @mswjs/interceptors to ^0.42 on the 14 line? Users on nock@14 currently hang on any client that sets Connection: keep-alive explicitly. In our case that is a PowerBI upload client and a docx service client, both of which set the header in application code, so there is nothing to change test-side — we are carrying a local patch of @mswjs/interceptors instead.
I appreciate 0.42 was an API-breaking release for the interceptors integration ([email protected] with 0.42 forced through throws clientRequestInterceptor.dispose is not a function from lib/recorder.js), so this may not be a small backport. Happy to help if you'd like it attempted — and equally happy to be told 15 is the answer and to wait.
Unrelated note on #2789
While investigating I found #2789 (replyWithError with an object) is marked resolved in 15.0.0-beta.8, but it still reproduces on 15.0.0-beta.14:
nock('https://example.test').get('/x').replyWithError({ message: 'boom' }) // hangs
nock('https://example.test').get('/x').replyWithError(new Error('boom')) // errors correctlyThe reason is dropped upstream in @mswjs/interceptors, which ignores any controller.errorWith() reason that is not an Error instance (mswjs/interceptors#625, still open). I have a fix ready to propose there. Flagging in case #2789 should be reopened.
Nock Version
14.0.17 (also verified against 14.0.0, 14.0.5, 14.0.10 and 15.0.0-beta.14)
Node Version
v20.20.2 and v22
Would you be interested in contributing a fix?
Yes.
Investigated and written by Claude (Anthropic), posting from @wagenet's account.
Source: nock/nock