Websocket error message: Invalid header value char

This issue has been tracked since 2022-10-26.

Describe the bug
I'm connecting with nodejs/puppeteer to a self-hosted browserless docker image. When the queue is full, a 429 is sent back to nodejs. However, the 429 response is not properly received and triggers a Websocket error "invalid header value char" on nodejs side:

ERROR  ErrorEvent {
  [Symbol(kTarget)]: WebSocket {
    _events: [Object: null prototype] {
      open: [Function],
      error: [Function]
    },
    _eventsCount: 2,
    _maxListeners: undefined,
    _binaryType: 'nodebuffer',
    _closeCode: 1006,
    _closeFrameReceived: false,
    _closeFrameSent: false,
    _closeMessage: <Buffer >,
    _closeTimer: null,
    _extensions: {},
    _paused: false,
    _protocol: '',
    _readyState: 3,
    _receiver: null,
    _sender: null,
    _socket: null,
    _bufferedAmount: 0,
    _isServer: false,
    _redirects: 0,
    _url: 'ws://browserless:3000',
    _originalIpc: false,
    _originalSecure: false,
    _originalHostOrSocketPath: 'browserless:3000',
    _req: null,
    [Symbol(kCapture)]: false
  },
  [Symbol(kType)]: 'error',
  [Symbol(kError)]: Error: Parse Error: Invalid header value char
      at Socket.socketOnData (node:_http_client:534:22)
      at Socket.emit (node:events:513:28)
      at Socket.emit (node:domain:489:12)
      at addChunk (node:internal/streams/readable:324:12)
      at readableAddChunk (node:internal/streams/readable:297:9)
      at Readable.push (node:internal/streams/readable:234:10)
      at TCP.onStreamRead (node:internal/stream_base_commons:190:23) {
    bytesParsed: 70,
    code: 'HPE_INVALID_HEADER_TOKEN',
    reason: 'Invalid header value char',
    rawPacket: <Buffer 48 54 54 50 2f 31 2e 31 20 34 32 39 20 54 6f 6f 20 4d 61 6e 79 20 52 65 71 75 65 73 74 73 0a 43 6f 6e 74 65 6e 74 2d 54 79 70 65 3a 20 74 65 78 74 2f ... 107 more bytes>
  },
  [Symbol(kMessage)]: 'Parse Error: Invalid header value char'
}

My assumption is that something in the HTTP-response compiled within rejectSocket is malformed and is now rejected by nodejs/Websocket in a recent version. Could be the header Content-Encoding which provides "UTF-8" as value. However, this is not a valid value for content encoding (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding).

To Reproduce
docker image: image: browserless/chrome:[email protected]:ca9c1a56ff88ba91c3aac908713cfe92f4e308c941a64e408d00d26bf1de036a

Consumer:
nodejs: node:[email protected]:32b920f09c28e3b27c9c5620d843aab0d2bb2f46e7d0505686626ba20cb5da0e
puppeteer-core: 19.1.0

Consumer code (shortened for brevity):

router.use('/pdf', async (req, res) => {
  let browser = null
  try {
    browser = await puppeteer.connect({browserWSEndpoint})
    const context = await browser.createIncognitoBrowserContext()
    const page = await context.newPage()
    page.setUserAgent('Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:96.0) Gecko/20100101 Firefox/96.0')
    await page.goto(url)
    const pdf = await page.pdf()
    browser.disconnect()
    res.contentType('application/pdf')
    res.send(pdf)
  } catch (error) {
    if (browser) {
      browser.disconnect()
    }
    let errorMessage = null
    if (error.error) {
      // error is a WebScocket ErrorEvent Object which contains an error property
      errorMessage = error.error.toString()
      res.status(503)
    } else {
      errorMessage = error.toString()
      res.status(500)
    }

    console.error(error)

    res.contentType('application/json')
    res.send({ error: errorMessage })
  }
})

Steps to reproduce the behavior:

  1. Start browserless with MAX_CONCURRENT_SESSIONS=1 and MAX_QUEUE_LENGTH=0
  2. Hit the /pdf route (code above) twice
  3. 2nd hit will trigger a 429 error on browserles side
  4. console.error(error) will output 'Parse Error: Invalid header value char' (full error message above)

Expected behavior
Expected to receive a proper HTTP-response with status code 429

usu wrote this answer on 2022-10-31

Just noticed I was running on an old version of browserless. Upgraded to 1.56.0-puppeteer-18.0.5 and now the error seems to match with what I expected:

ERROR  ErrorEvent {
  [Symbol(kTarget)]: WebSocket {
    _events: [Object: null prototype] {
      open: [Function],
      error: [Function]
    },
    _eventsCount: 2,
    _maxListeners: undefined,
    _binaryType: 'nodebuffer',
    _closeCode: 1006,
    _closeFrameReceived: false,
    _closeFrameSent: false,
    _closeMessage: <Buffer >,
    _closeTimer: null,
    _extensions: {},
    _paused: false,
    _protocol: '',
    _readyState: 3,
    _receiver: null,
    _sender: null,
    _socket: null,
    _bufferedAmount: 0,
    _isServer: false,
    _redirects: 0,
    _url: 'ws://browserless:3000',
    _originalIpc: false,
    _originalSecure: false,
    _originalHostOrSocketPath: 'browserless:3000',
    _req: ClientRequest {
      _events: [Object: null prototype],
      _eventsCount: 4,
      _maxListeners: undefined,
      outputData: [],
      outputSize: 0,
      writable: true,
      destroyed: true,
      _last: true,
      chunkedEncoding: false,
      shouldKeepAlive: false,
      maxRequestsOnConnectionReached: false,
      _defaultKeepAlive: true,
      useChunkedEncodingByDefault: false,
      sendDate: false,
      _removedConnection: false,
      _removedContLen: false,
      _removedTE: false,
      strictContentLength: false,
      _contentLength: 0,
      _hasBody: true,
      _trailer: '',
      finished: true,
      _headerSent: true,
      _closed: false,
      socket: [Socket],
      _header: 'GET / HTTP/1.1\r\n' +
        'User-Agent: Puppeteer 19.1.0\r\n' +
        'Sec-WebSocket-Version: 13\r\n' +
        'Sec-WebSocket-Key: ORIpk17BKH1vooGr3yNDcQ==\r\n' +
        'Connection: Upgrade\r\n' +
        'Upgrade: websocket\r\n' +
        'Host: browserless:3000\r\n' +
        '\r\n',
      _keepAliveTimeout: 0,
      _onPendingData: [Function: nop],
      agent: undefined,
      socketPath: undefined,
      method: 'GET',
      maxHeaderSize: undefined,
      insecureHTTPParser: undefined,
      path: '/',
      _ended: false,
      res: [IncomingMessage],
      aborted: true,
      timeoutCb: null,
      upgradeOrConnect: false,
      parser: [HTTPParser],
      maxHeadersCount: null,
      reusedSocket: false,
      host: 'browserless',
      protocol: 'http:',
      [Symbol(kCapture)]: false,
      [Symbol(kBytesWritten)]: 0,
      [Symbol(kEndCalled)]: true,
      [Symbol(kNeedDrain)]: false,
      [Symbol(corked)]: 0,
      [Symbol(kOutHeaders)]: [Object: null prototype],
      [Symbol(kUniqueHeaders)]: null,
      [Symbol(kAborted)]: true,
      [Symbol(kError)]: undefined
    },
    [Symbol(kCapture)]: false
  },
  [Symbol(kType)]: 'error',
  [Symbol(kError)]: Error: Unexpected server response: 429
      at ClientRequest.<anonymous> (/app/node_modules/ws/lib/websocket.js:886:7)
      at ClientRequest.emit (node:events:513:28)
      at ClientRequest.emit (node:domain:489:12)
      at HTTPParser.parserOnIncomingClient [as onIncoming] (node:_http_client:693:27)
      at HTTPParser.parserOnHeadersComplete (node:_http_common:117:17)
      at Socket.socketOnData (node:_http_client:534:22)
      at Socket.emit (node:events:513:28)
      at Socket.emit (node:domain:489:12)
      at addChunk (node:internal/streams/readable:324:12)
      at readableAddChunk (node:internal/streams/readable:297:9)
      at Readable.push (node:internal/streams/readable:234:10)
      at TCP.onStreamRead (node:internal/stream_base_commons:190:23),
  [Symbol(kMessage)]: 'Unexpected server response: 429'
}

Might still be worth to verify, if Content-Encoding is properly used above.

joelgriffith wrote this answer on 2022-11-01

Yeah this came up a few month back from what I remember... I'll double check it. Writing HTTP responses in lower level socket methods isn't a fun thing :)

More Details About Repo
Owner Name browserless
Repo Name chrome
Full Name browserless/chrome
Language TypeScript
Created Date 2017-11-17
Updated Date 2023-03-22
Star Count 5309
Watcher Count 47
Fork Count 516
Issue Count 29

YOU MAY BE INTERESTED

Issue Title Created Date Updated Date