Skip to content

Streaming uploads cause truncated responses in node 16 #4262

@achingbrain

Description

@achingbrain

Support plan

  • is this issue currently blocking your project? (yes/no): yes
  • is this issue affecting a production system? (yes/no): yes

Context

  • node version: 16
  • module version with issue: 20.1.4
  • last module version without issue: Unsure
  • environment (e.g. node, browser, native): node
  • used with (e.g. hapi application, another framework, standalone, ...): hapi application
  • any other relevant information:

What are you trying to achieve or the steps to reproduce?

Take a look at the following code sample. It starts a server, makes a request to the server, sends some data, and finally prints out the response.

It has two methods of reading the data from the client, one treating request.payload as an async iterator, one treating it as a readable stream.

const Hapi = require('@hapi/hapi')
const { PassThrough } = require('stream')
const http = require('http')

function receiveDataAsStream (request, stream) {
  request.payload.on('data', () => {
    console.info('receive data')
  })
  request.payload.on('end', () => {
    console.info('end data')

    // send result to client
    stream.end('hello world')
  })
}

async function receiveDataAsAsyncIterator (request, stream) {
  for await (const buf of request.payload) {
    console.info('receive data')
  }

  // send result to client
  stream.end('hello world')
}

async function main () {
  const server = Hapi.server({
    port: 0,
    host: 'localhost'
  })

  server.route({
    method: 'POST',
    path: '/',
    options: {
      payload: {
        output: 'stream'
      }
    },
    handler: (request, h) => {
      const stream = new PassThrough()

      // simulate async work
      process.nextTick(() => {
        // works on node 14 and node 16
        // receiveDataAsStream(request, stream)

        // works on node 14, breaks on node 16
        receiveDataAsAsyncIterator(request, stream).catch(err => console.error(err))
      })

      return h.response(stream)
    }
  })

  await server.start()

  // make a request
  const req = http.request({
    hostname: 'localhost',
    port: server.info.port,
    method: 'POST'
  }, (res) => {
    res.on('data', (chunk) => {
      console.info(chunk.toString())
    })
    res.on('end', () => {
      console.info('response ended')
      server.stop().catch(err => console.error(err))
    })
  })

  console.info('send data')
  req.end('{}')
}

main().catch(err => {
  console.error(err)
  process.exit(1)
})

On node 16 the 'hello world' output is not received by the client, on node 14 it is.

I think this is something to do with how the request payload is consumed as when you switch the receiveDataAsAsyncIterator function out for receiveDataAsStream it works as expected on node 14 and node 16.

What was the result you got?

Node 14:

$ node .
send data
receive data
hello world
response ended

Node 16:

$ node .
send data
receive data
response ended

The 'hello world' string is missing from the node 16 output.

What result did you expect?

Node 14:

$ node .
send data
receive data
hello world
response ended

Node 16:

$ node .
send data
receive data
hello world
response ended

The 'hello world' string should be included in the node 16 output.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugBug or defect

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions