CodeGenieApp/serverless-express

Getting "stream is not readable" since 4.16.0

lightpriest opened this issue · 0 comments

The #692 PR introduced a bug when using with express and an async middleware: the IncomingMessage/Request (stream) is marked as not readable and fails when used with body-parser (for example).

Affected version: 4.16.0
Express version: 4 (didn't test it on 5)
Body parser version: 1.20.3

For the code attached, running with 4.15.0 yields:

{
  statusCode: 200,
  body: 'Hello World!',
  isBase64Encoded: false,
  headers: {
    'x-powered-by': 'Express',
    'content-type': 'text/html; charset=utf-8',
    'content-length': '12',
    etag: 'W/"c-Lve95gjOVATpfV8EL5X4nxwjKHE"'
  }
}

Running with 4.16.0 yields:

{
  statusCode: 500,
  body: '<!DOCTYPE html>\n' +
    '<html lang="en">\n' +
    '<head>\n' +
    '<meta charset="utf-8">\n' +
    '<title>Error</title>\n' +
    '</head>\n' +
    '<body>\n' +
    '<pre>InternalServerError: stream is not readable<br> &nbsp; &nbsp;at readStream (node_modules/raw-body/index.js:185:17)<br> &nbsp; &nbsp;at getRawBody ... (same as below)' +
    '</body>\n' +
    '</html>\n',
  isBase64Encoded: false,
  headers: {
    'x-powered-by': 'Express',
    'content-security-policy': "default-src 'none'",
    'x-content-type-options': 'nosniff',
    'content-type': 'text/html; charset=utf-8',
    'content-length': 1262
  }
}
InternalServerError: stream is not readable
    at readStream (node_modules/raw-body/index.js:185:17)
    at getRawBody (node_modules/raw-body/index.js:116:12)
    at read (node_modules/body-parser/lib/read.js:79:3)
    at jsonParser (node_modules/body-parser/lib/types/json.js:138:5)
    at Layer.handle [as handle_request] (node_modules/express/lib/router/layer.js:95:5)
    at trim_prefix (node_modules/express/lib/router/index.js:328:13)
    at node_modules/express/lib/router/index.js:286:9
    at Function.process_params (node_modules/express/lib/router/index.js:346:12)
    at next (node_modules/express/lib/router/index.js:280:10)
    at src/example.ts:10:3

Code to reproduce:

import serverlessExpress from '@codegenie/serverless-express';
import express from 'express';
import bodyParser from 'body-parser';
import { promisify } from 'node:util';

const app = express();

app.use(async (req, res, next) => {
  // Remove this line to "remove the problem"
  await promisify(setTimeout)(1000);
  next();
});

app.use(bodyParser.json());

app.use((req, res) => {
  res.send('Hello World!');
});

const serverlessExpressInstance = serverlessExpress({ app });

export async function handler(event: any, context: any, callback: any) {
  return serverlessExpressInstance(event, context, callback);
}

handler(
  {
    version: '2.0',
    rawPath: '/path/to/resource',
    headers: {
      "Content-Type": "application/json",
      "Content-Length": "2",
    },
    requestContext: {
      http: {
        method: 'POST',
        path: '/path/to/resource',
        protocol: 'HTTP/1.1',
        sourceIp: '192.168.0.1/32',
        userAgent: 'agent',
      },
    },
    body: "{}",
    isBase64Encoded: false,
  },
  {} as any,
  () => {},
)
  .then(console.log.bind(console))
  .catch(console.error.bind(console));

I believe this is the culprit. Express doesn't return a promise from the handle function, so if there's a middleware that starts an async operation the function will return and the stream will be marked as not readable.

  await framework.sendRequest({ app, request, response })
  markHttpRequestAsCompleted(request) // <-- maybe this should be moved down to after the the stream completes?
  await waitForStreamComplete(response)

Small note, initially I had trouble finding this because I thought we were actually using vendia's serverless-express version. After fiddling with it for a couple of hours I realized it was actually "exporting" codegenie's version. So leaving this note here, in case someone else thinks they're using vendia's.