Skip to content

client.request throws Error with setEncoding #1125

@koh110

Description

@koh110

Bug Description

client.request throws an error when body.json() & body.text() are called with setEncoding.

Reproducible By

import { createServer } from 'http'
import { Client } from 'undici'
import { once } from 'events'
import assert from 'assert'

const data = 'a'.repeat(100)

const server = createServer((request, response) => {
  response.end(JSON.stringify({ data }))
}).listen()

await once(server, 'listening')

const client = new Client(`http://localhost:${server.address().port}`)

try {
  const { body, headers, statusCode } = await client.request({
    path: '/',
    method: 'GET'
  })
  console.log(`response received ${statusCode}`)
  console.log('headers', headers)
  body.setEncoding('utf8')
  const json = await body.json()
  assert.strictEqual(json.data, data, 'multi byte')
} catch (error) {
  console.error('error!!:', error)
} finally {
  client.close()
  server.close()
}
$ node index.mjs
error!!: TypeError [ERR_INVALID_ARG_TYPE]: The "list[0]" argument must be an instance of Buffer or Uint8Array. Received type string ('{"data":"aaaaaaaaaaaaaaa...)
    at new NodeError (node:internal/errors:371:5)
    at Function.concat (node:buffer:559:13)
    at consumeEnd (/Users/kohtaito/dev/tmp/test_undici/node_modules/undici/lib/api/readable.js:238:33)
    at BodyReadable.<anonymous> (/Users/kohtaito/dev/tmp/test_undici/node_modules/undici/lib/api/readable.js:220:7)
    at BodyReadable.emit (node:events:390:28)
    at BodyReadable.emit (/Users/kohtaito/dev/tmp/test_undici/node_modules/undici/lib/api/readable.js:66:18)
    at endReadableNT (node:internal/streams/readable:1343:12)
    at processTicksAndRejections (node:internal/process/task_queues:83:21) {
  code: 'ERR_INVALID_ARG_TYPE'
}

Expected Behavior

Logs & Screenshots

Environment

Node.js v16.13.1
undici v4.11.0

Additional context

chunk is converted to a string by StringDecoder only when consumeStart is executed.

$ git diff
diff --git a/lib/api/readable.js b/lib/api/readable.js
index 3bb454e..8ea758e 100644
--- a/lib/api/readable.js
+++ b/lib/api/readable.js
@@ -94,6 +94,7 @@ module.exports = class BodyReadable extends Readable {

   push (chunk) {
     if (this[kConsume] && chunk !== null && !this[kReading]) {
+      console.log('BodyReadable: push', typeof chunk, this.readableEncoding)
       consumePush(this[kConsume], chunk)
       return true
     } else {
@@ -196,6 +197,7 @@ function consumeStart (consume) {
   const { _readableState: state } = consume.stream

   for (const chunk of state.buffer) {
+    console.log('consumeStart', typeof chunk, consume.stream.readableEncoding)
     consumePush(consume, chunk)
   }
$ node index.mjs
response received 200
headers {
  date: 'Mon, 06 Dec 2021 09:08:48 GMT',
  connection: 'keep-alive',
  'keep-alive': 'timeout=5',
  'content-length': '10000011'
}
consumeStart string utf8
BodyReadable: push object utf8
BodyReadable: push object utf8
BodyReadable: push object utf8
BodyReadable: push object utf8
BodyReadable: push object utf8

I think that this code needs to use consume.stream._readableState.decoder.end() in consumeEnd instead of Buffer.concat(body) if the value is in readableEncoding.
Is this the right way to fix it?

I also considered a method to overwrite StringDecoder so that it is not created in setEncoding.
However, it seems that it will be difficult to upgrade because it depends too much on the structure of Stream.

Ref: #1119

CC: @mcollina

Metadata

Metadata

Assignees

No one assigned

    Labels

    Status: help-wantedThis issue/pr is open for contributionsbugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions