Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 26 additions & 2 deletions lib/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,12 @@ class Parser {
}
}

finish () {
currentParser = this
llhttp.exports.llhttp_finish(this.ptr)
currentParser = null
}

destroy () {
assert(this.ptr != null)
assert(currentParser == null)
Expand Down Expand Up @@ -794,9 +800,9 @@ class Parser {
}

onMessageComplete () {
const { client, socket, statusCode, upgrade, trailers, headers: rawTrailers } = this
const { client, socket, statusCode, upgrade, trailers, headers: rawTrailers, shouldKeepAlive } = this

if (socket.destroyed) {
if (socket.destroyed && (!statusCode || shouldKeepAlive)) {
Comment thread
ronag marked this conversation as resolved.
Comment thread
ronag marked this conversation as resolved.
return -1
}

Expand Down Expand Up @@ -901,6 +907,16 @@ function onSocketConnect () {
}

function onSocketError (err) {
const { [kParser]: parser } = this

// On Mac OS, we get an ECONNRESET even if there is a full body to be forwarded
// to the user.
if (err.code === 'ECONNRESET' && parser.statusCode && !parser.shouldKeepAlive) {
Comment thread
ronag marked this conversation as resolved.
Comment thread
ronag marked this conversation as resolved.
// We treat all incoming data so for as a valid response.
parser.finish()
return
}

const { [kClient]: client } = this

this[kError] = err
Expand Down Expand Up @@ -932,6 +948,14 @@ function onSocketError (err) {
}

function onSocketEnd () {
const { [kParser]: parser } = this

if (parser.statusCode && !parser.shouldKeepAlive) {
// We treat all incoming data so for as a valid response.
parser.finish()
return
}
Comment thread
ronag marked this conversation as resolved.

util.destroy(this, new SocketError('other side closed'))
}

Expand Down
84 changes: 84 additions & 0 deletions test/client-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const { createServer } = require('http')
const EE = require('events')
const { kConnect } = require('../lib/core/symbols')
const { Readable } = require('stream')
const net = require('net')
const { promisify } = require('util')

test('request abort before headers', (t) => {
t.plan(6)
Expand Down Expand Up @@ -97,3 +99,85 @@ test('trailers', (t) => {
})
})
})

test('destroy socket abruptly', async (t) => {
t.plan(2)

const server = net.createServer((socket) => {
const lines = [
'HTTP/1.1 200 OK',
'Date: Sat, 09 Oct 2010 14:28:02 GMT',
'Connection: close',
Comment thread
ronag marked this conversation as resolved.
'',
'the body'
Comment thread
ronag marked this conversation as resolved.
]
socket.end(lines.join('\r\n'))

// Unfortunately calling destroy synchronously might get us flaky results,
// therefore we delay it to the next event loop run.
setImmediate(socket.destroy.bind(socket))
})
t.teardown(server.close.bind(server))

await promisify(server.listen.bind(server))(0)
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.close.bind(client))

const { statusCode, body } = await client.request({
path: '/',
method: 'GET'
})

t.equal(statusCode, 200)

body.setEncoding('utf8')

let actual = ''

for await (const chunk of body) {
actual += chunk
}

t.equal(actual, 'the body')
})

test('destroy socket abruptly with keep-alive', async (t) => {
Comment thread
ronag marked this conversation as resolved.
t.plan(2)

const server = net.createServer((socket) => {
const lines = [
'HTTP/1.1 200 OK',
'Date: Sat, 09 Oct 2010 14:28:02 GMT',
'Connection: keep-alive',
'',
'the body'
]
socket.end(lines.join('\r\n'))

// Unfortunately calling destroy synchronously might get us flaky results,
// therefore we delay it to the next event loop run.
setImmediate(socket.destroy.bind(socket))
})
t.teardown(server.close.bind(server))

await promisify(server.listen.bind(server))(0)
const client = new Client(`http://localhost:${server.address().port}`)
t.teardown(client.close.bind(client))

const { statusCode, body } = await client.request({
path: '/',
method: 'GET'
})

t.equal(statusCode, 200)

body.setEncoding('utf8')

let actual = ''

for await (const chunk of body) {
actual += chunk
}

t.equal(actual, 'the body')
})
2 changes: 2 additions & 0 deletions test/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -1004,6 +1004,8 @@ test('connected', (t) => {
t.plan(7)

const server = createServer((req, res) => {
// needed so that disconnect is emitted
res.setHeader('connection', 'close')
req.pipe(res)
})
t.teardown(server.close.bind(server))
Expand Down