Skip to content

Commit ac2fbb9

Browse files
committed
improve spec compliance by properly handling responses without content-length or transfer-encoding
fixes #1414 #1412 #1490
1 parent c485884 commit ac2fbb9

2 files changed

Lines changed: 49 additions & 3 deletions

File tree

lib/client.js

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,7 @@ class Parser {
419419
this.headersSize = 0
420420
this.headersMaxSize = client[kMaxHeadersSize]
421421
this.shouldKeepAlive = false
422+
this.hasTransferEncoding = false
422423
this.paused = false
423424
this.resume = this.resume.bind(this)
424425

@@ -615,6 +616,8 @@ class Parser {
615616
this.keepAlive += buf.toString()
616617
} else if (key.length === 14 && key.toString().toLowerCase() === 'content-length') {
617618
this.contentLength += buf.toString()
619+
} else if (key.length === 16 && key.toString().toLowerCase() === 'transfer-encoding') {
620+
this.hasTransferEncoding = true
618621
}
619622

620623
this.trackHeader(buf.length)
@@ -957,10 +960,16 @@ function onSocketEnd () {
957960
}
958961

959962
function onSocketClose () {
960-
const { [kClient]: client } = this
963+
let { [kClient]: client, [kParser]: parser } = this
961964

962-
this[kParser].destroy()
963-
this[kParser] = null
965+
// without content-length or transfer-encoding header, the body is defined as everything
966+
// the server sends before closing the connection. RFC 7230 3.3.3 point 7.
967+
if (!parser.contentLength && !parser.hasTransferEncoding) {
968+
parser.onMessageComplete()
969+
}
970+
971+
parser.destroy()
972+
parser = null
964973

965974
const err = this[kError] || new SocketError('closed', util.getSocketInfo(this))
966975

test/client.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1972,6 +1972,43 @@ test('async iterator yield object error', (t) => {
19721972
})
19731973
})
19741974

1975+
test('Successfully get a Response when neither a Transfer-Encoding or Content-Length header is present', (t) => {
1976+
t.plan(2)
1977+
const server = createServer((req, res) => {
1978+
req.on('data', (data) => {
1979+
})
1980+
req.on('end', () => {
1981+
res.removeHeader('transfer-encoding')
1982+
res.writeHead(200, {
1983+
// Header isn't actually necessary, but tells node to close after response
1984+
connection: 'close',
1985+
foo: 'bar'
1986+
})
1987+
res.end('a response body')
1988+
})
1989+
})
1990+
t.teardown(server.close.bind(server))
1991+
1992+
server.listen(0, () => {
1993+
const client = new Client(`http://localhost:${server.address().port}`)
1994+
t.teardown(client.close.bind(client))
1995+
1996+
client.request({ path: '/', method: 'GET' }, (err, { body }) => {
1997+
t.error(err)
1998+
const bufs = []
1999+
body.on('error', () => {
2000+
t.fail('Closing the connection is valid')
2001+
})
2002+
body.on('data', (buf) => {
2003+
bufs.push(buf)
2004+
})
2005+
body.on('end', () => {
2006+
t.equal('a response body', Buffer.concat(bufs).toString('utf8'))
2007+
})
2008+
})
2009+
})
2010+
})
2011+
19752012
function buildParams (path) {
19762013
const cleanPath = path.replace('/?', '').replace('/', '').split('&')
19772014
const builtParams = cleanPath.reduce((acc, entry) => {

0 commit comments

Comments
 (0)