CodeGenieApp / serverless-express

Run Express and other Node.js frameworks on AWS Serverless technologies such as Lambda, API Gateway, Lambda@Edge, and more.
https://codegenie.codes
Apache License 2.0
5.13k stars 668 forks source link

express.static doesnt work with ALB endpoint #563

Open ml27299 opened 1 year ago

ml27299 commented 1 year ago

When using express.static, the files within the static path do not load and the endpoint (Application Load Balancer) returns a 502. app.use(express.static(path.resolve("./public")));

I was trying to add a robots.txt to the static path and let express.static handle the routing and response, but when I try to load the page, it responds with a 502 Bad Gateway. I'm 100% sure the file exists and the static path is correct, I've logged it. Also, when I try to load an asset that isnt in the static path, like /robo.txt, it responds with a 404, so it's recognizing something.

I've looked into what can cause a 502 from ALB, some docs on AWS says that either the response is larger than 1MB or the lambda timed out, which neither of these things is the case in my situation. My speculation is maybe the response from express.static isnt being handled correctly for an ALB in "vendia/serverless-express"? Possibly a content-type header issue? I've run the app locally via serverless offline and as a stand alone express server, in both cases, the /robots.txt loads fine, which is why I think it might have something to do with how ALB is integrated with the library.

Anyways, I worked around this issue by just explicitly setting the robots.txt path and importing the txt file

import txt from "../../robots.txt";

app.use("/robots.txt", (req, res, next) => {
    res.set("Content-Type", "text/plain");
    res.send(txt);
});
H4ad commented 1 year ago

Try enabling the log level option to debug, then you can see if the problem is due to response size.

vendia/serverless doesn't interfere with express.static because it just waits for the response to be complete after forwarding to express, the problem may be in the ServerlessResponse class created to forward the response to express but I don't know, get the debug logs and post here to see if we can find the problem.

ml27299 commented 1 year ago

Oh cool, didnt realize there was a debug option. Sure here are the logs, so it does look like it's getting the file

{
  message: 'SERVERLESS_EXPRESS:PROXY',
  event: '{\n' +
    '  requestContext: {\n' +
    '    elb: {\n' +
    "      targetGroupArn: 'arn:aws:elasticloadbalancing:us-east-2::targetgroup/'\n" +
    '    }\n' +
    '  },\n' +
    "  httpMethod: 'GET',\n" +
    "  path: '/robots.txt',\n" +
    '  queryStringParameters: {},\n' +
    '  headers: {\n' +
    "    accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',\n" +
    "    'accept-encoding': 'gzip, deflate',\n" +
    "    'accept-language': 'en-US,en;q=0.9',\n" +
    "    'cache-control': 'max-age=0',\n" +
    "    connection: 'keep-alive',\n" +
    "    cookie: 'G_ENABLED_IDPS=google;\n" +
    "    host: 'REMOVED',\n" +
    "    'upgrade-insecure-requests': '1',\n" +
    "    'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36',\n" +
    "    'x-amzn-trace-id': 'Root=1-6354e2d3-3336155837ebfe9f3fabfd55',\n" +
    "    'x-forwarded-for': '72.191.67.12',\n" +
    "    'x-forwarded-port': '80',\n" +
    "    'x-forwarded-proto': 'http'\n" +
    '  },\n' +
    "  body: '',\n" +
    '  isBase64Encoded: false\n' +
    '}',
  context: '{\n' +
    '  callbackWaitsForEmptyEventLoop: [Getter/Setter],\n' +
    '  succeed: [Function (anonymous)],\n' +
    '  fail: [Function (anonymous)],\n' +
    '  done: [Function (anonymous)],\n' +
    "  functionVersion: '$LATEST',\n" +
    "  functionName: 'react-stage-tutors',\n" +
    "  memoryLimitInMB: '1024',\n" +
    "  logGroupName: '/aws/lambda/react-stage-tutors',\n" +
    "  logStreamName: '2022/10/23/[$LATEST]955c8b59005d40e28f1694a69a909a40',\n" +
    '  clientContext: undefined,\n' +
    '  identity: undefined,\n' +
    "  invokedFunctionArn: 'arn:aws:lambda:us-east-2::function:,\n" +
    "  awsRequestId: '',\n" +
    '  getRemainingTimeInMillis: [Function: getRemainingTimeInMillis],\n' +
    '  serverlessSdk: {\n' +
    '    captureError: [Function (anonymous)],\n' +
    '    span: [Function (anonymous)],\n' +
    '    tagEvent: [Function (anonymous)],\n' +
    '    setEndpoint: [Function (anonymous)],\n' +
    '    getTransactionId: [Function (anonymous)],\n' +
    '    getDashboardUrl: [Function (anonymous)]\n' +
    '  },\n' +
    '  captureError: [Function (anonymous)],\n' +
    '  span: [Function (anonymous)]\n' +
    '}',
  resolutionMode: 'PROMISE',
  eventSourceName: 'AWS_ALB',
  binarySettings: {
    contentTypes: [
      'application/javascript',
      'application/json',
      'application/octet-stream',
      'application/xml',
      'font/eot',
      'font/opentype',
      'font/otf',
      'image/jpeg',
      'image/png',
      'image/svg+xml',
      'text/comma-separated-values',
      'text/css',
      'text/html',
      'text/javascript',
      'text/plain',
      'text/text',
      'text/xml'
    ]
  },
  respondWithErrors: true
}
{
  message: 'SERVERLESS_EXPRESS:FORWARD_REQUEST_TO_NODE_SERVER:REQUEST_VALUES',
  requestValues: {
    method: 'GET',
    headers: {
      accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
      'accept-encoding': 'gzip, deflate',
      'accept-language': 'en-US,en;q=0.9',
      'cache-control': 'max-age=0',
      connection: 'keep-alive',
      cookie: 'G_ENABLED_IDPS=google;',
      host: 'REMOVED',
      'upgrade-insecure-requests': '1',
      'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36',
      'x-amzn-trace-id': 'Root=1-6354e2d3-3336155837ebfe9f3fabfd55',
      'x-forwarded-for': '72.191.67.12',
      'x-forwarded-port': '80',
      'x-forwarded-proto': 'http'
    },
    body: undefined,
    remoteAddress: '',
    path: '/robots.txt'
  }
}
2022-10-23T06:44:37.430Z    5837fbec-9025-43ca-870c-6dd7db94d275    DEBUG   {
  message: 'SERVERLESS_EXPRESS:FORWARD_REQUEST_TO_NODE_SERVER:RESPONSE',
  response: <ref *1> ServerResponse {
    _events: [Object: null prototype] {},
    _eventsCount: 0,
    _maxListeners: undefined,
    outputData: [],
    outputSize: 0,
    writable: true,
    destroyed: false,
    _last: false,
    chunkedEncoding: false,
    shouldKeepAlive: true,
    _defaultKeepAlive: true,
    useChunkedEncodingByDefault: false,
    sendDate: true,
    _removedConnection: false,
    _removedContLen: false,
    _removedTE: false,
    _contentLength: null,
    _hasBody: true,
    _trailer: '',
    finished: true,
    _headerSent: true,
    socket: {
      _writableState: [Object],
      writable: true,
      on: {},
      removeListener: {},
      destroy: {},
      cork: {},
      uncork: {},
      write: [Function: write],
      _httpMessage: [Circular *1]
    },
    _header: 'HTTP/1.1 200 OK\r\n' +
      'X-DNS-Prefetch-Control: off\r\n' +
      'X-Frame-Options: SAMEORIGIN\r\n' +
      'Strict-Transport-Security: max-age=15552000; includeSubDomains\r\n' +
      'X-Download-Options: noopen\r\n' +
      'X-Content-Type-Options: nosniff\r\n' +
      'X-XSS-Protection: 1; mode=block\r\n' +
      'Referrer-Policy: same-origin\r\n' +
      'Surrogate-Control: no-store\r\n' +
      'Cache-Control: no-store, no-cache, must-revalidate, proxy-revalidate\r\n' +
      'Pragma: no-cache\r\n' +
      'Expires: 0\r\n' +
      'Accept-Ranges: bytes\r\n' +
      'Last-Modified: Tue, 01 Jan 1980 00:00:00 GMT\r\n' +
      'ETag: W/"1ec-4977387000"\r\n' +
      'Content-Type: text/plain; charset=UTF-8\r\n' +
      'Content-Length: 492\r\n' +
      'Date: Sun, 23 Oct 2022 06:44:37 GMT\r\n' +
      'Connection: keep-alive\r\n' +
      '\r\n',
    _keepAliveTimeout: 0,
    _onPendingData: [Function: noopPendingOutput],
    _sent100: false,
    _expect_continue: false,
    req: IncomingMessage {
      _readableState: [ReadableState],
      _events: [Object: null prototype] {},
      _eventsCount: 0,
      _maxListeners: undefined,
      socket: [Object],
      httpVersionMajor: '1',
      httpVersionMinor: '1',
      httpVersion: '1.1',
      complete: true,
      headers: [Object],
      rawHeaders: [],
      trailers: {},
      rawTrailers: [],
      aborted: false,
      upgrade: null,
      url: '/robots.txt',
      method: 'GET',
      statusCode: null,
      statusMessage: null,
      client: [Object],
      _consuming: false,
      _dumped: false,
      ip: '',
      body: {},
      _read: [Function (anonymous)],
      next: [Function: next],
      baseUrl: '',
      originalUrl: '/robots.txt',
      _parsedUrl: [Url],
      params: {},
      query: {},
      res: [Circular *1],
      secret: undefined,
      cookies: [Object],
      signedCookies: [Object: null prototype] {},
      csrfToken: [Function: csrfToken],
      context: [Object],
      apolloClient: [ApolloClient],
      apolloClientBatch: [ApolloClient],
      staticContext: [Object],
      setStaticContext: [Function (anonymous)],
      setAuthCookie: [Function (anonymous)],
      clearAuthCookie: [Function (anonymous)],
      _parsedOriginalUrl: [Url],
      [Symbol(kCapture)]: false
    },
    locals: [Object: null prototype] {},
    setStaticContext: [Function (anonymous)],
    renderServerError: [Function (anonymous)],
    serverError: [Function (anonymous)],
    __onFinished: null,
    statusMessage: 'OK',
    statusCode: 200,
    _wroteHeader: true,
    [Symbol(kCapture)]: false,
    [Symbol(kNeedDrain)]: true,
    [Symbol(corked)]: 0,
    [Symbol(kOutHeaders)]: [Object: null prototype] {
      'x-dns-prefetch-control': [Array],
      'x-frame-options': [Array],
      'strict-transport-security': [Array],
      'x-download-options': [Array],
      'x-content-type-options': [Array],
      'x-xss-protection': [Array],
      'referrer-policy': [Array],
      'surrogate-control': [Array],
      'cache-control': [Array],
      pragma: [Array],
      expires: [Array],
      'accept-ranges': [Array],
      'last-modified': [Array],
      etag: [Array],
      'content-type': [Array],
      'content-length': [Array]
    },
    [Symbol(Response body)]: [
      <Buffer 55 73 65 72 2d 41 67 65 6e 74 3a 20 4f 6d 6e 69 45 78 70 6c 6f 72 65 72 5f 42 6f 74 0a 44 69 73 61 6c 6c 6f 77 3a 20 2f 0a 0a 55 73 65 72 2d 61 67 65 ... 442 more bytes>,
      <Buffer >
    ],
    [Symbol(Response headers)]: {}
  }
}
{
  message: 'SERVERLESS_EXPRESS:FORWARD_RESPONSE:EVENT_SOURCE_RESPONSE_PARAMS',
  statusCode: 200,
  body: '[BASE64_ENCODED]',
  headers: [Object: null prototype] {
    'x-dns-prefetch-control': 'off',
    'x-frame-options': 'SAMEORIGIN',
    'strict-transport-security': 'max-age=15552000; includeSubDomains',
    'x-download-options': 'noopen',
    'x-content-type-options': 'nosniff',
    'x-xss-protection': '1; mode=block',
    'referrer-policy': 'same-origin',
    'surrogate-control': 'no-store',
    'cache-control': 'no-store, no-cache, must-revalidate, proxy-revalidate',
    pragma: 'no-cache',
    expires: '0',
    'accept-ranges': 'bytes',
    'last-modified': 'Tue, 01 Jan 1980 00:00:00 GMT',
    etag: 'W/"1ec-4977387000"',
    'content-type': 'text/plain; charset=UTF-8',
    'content-length': 492
  },
  isBase64Encoded: true
}
{
  message: 'SERVERLESS_EXPRESS:FORWARD_RESPONSE:EVENT_SOURCE_RESPONSE',
  successResponse: '{\n' +
    '  statusCode: 200,\n' +
    "  body: 'VXNlci1BZ2VudDogT21uaUV4cGxvcmVyX0JvdApEaXNhbGxvdzogLwoKVXNlci1hZ2VudDogKgpEaXNhbGxvdzogL3Byb3MKRGlzYWxsb3c6IC9jbGllbnRzLwpEaXNhbGxvdzogL2NsaWVudApEaXNhbGxvdzogL2FkbWluLwpEaXNhbGxvdzogL2xvZ291dApEaXNhbGxvdzogL2xlYWQKRGlzYWxsb3c6IC91cGRhdGUKRGlzYWxsb3c6IC9xdW90ZQpEaXNhbGxvdzogL3NpZ251cApEaXNhbGxvdzogL3NlYXJjaApEaXNhbGxvdzogL3NlYXJjaC1tYXAKRGlzYWxsb3c6IC9pbnZpdGUKRGlzYWxsb3c6IC9yZXF1ZXN0CkRpc2FsbG93OiAvd2VsY29tZQpEaXNhbGxvdzogL3Byby9zaWdudXAKRGlzYWxsb3c6IC9hdXRoCkRpc2FsbG93OiAvcHJvdmlkZXJzCkRpc2FsbG93OiAvcHJvL25leHQvc3RlcApEaXNhbGxvdzogL2NoYXQKRGlzYWxsb3c6IC9jaGVjawpEaXNhbGxvdzogL2Nkbi1jZ2kvY2hhbGxlbmdlLXBsYXRmb3JtLwpEaXNhbGxvdzogL2Nkbi1jZ2kvYm0vY3Yv',\n" +
    '  headers: {\n' +
    "    'x-dns-prefetch-control': 'off',\n" +
    "    'x-frame-options': 'SAMEORIGIN',\n" +
    "    'strict-transport-security': 'max-age=15552000; includeSubDomains',\n" +
    "    'x-download-options': 'noopen',\n" +
    "    'x-content-type-options': 'nosniff',\n" +
    "    'x-xss-protection': '1; mode=block',\n" +
    "    'referrer-policy': 'same-origin',\n" +
    "    'surrogate-control': 'no-store',\n" +
    "    'cache-control': 'no-store, no-cache, must-revalidate, proxy-revalidate',\n" +
    "    pragma: 'no-cache',\n" +
    "    expires: '0',\n" +
    "    'accept-ranges': 'bytes',\n" +
    "    'last-modified': 'Tue, 01 Jan 1980 00:00:00 GMT',\n" +
    `    etag: 'W/"1ec-4977387000"',\n` +
    "    'content-type': 'text/plain; charset=UTF-8',\n" +
    "    'content-length': 492\n" +
    '  },\n' +
    '  multiValueHeaders: undefined,\n' +
    '  isBase64Encoded: true\n' +
    '}',
  body: '[BASE64_ENCODED]'
}
H4ad commented 1 year ago

Yes, there is no problem on the express side and the SERVERLESS_EXPRESS:FORWARD_RESPONSE:EVENT_SOURCE_RESPONSE seems correct to me.

One thing you can do now is run express with the option that works and try to see the logs, and compare the SERVERLESS_EXPRESS:FORWARD_RESPONSE:EVENT_SOURCE_RESPONSE and try to see if there is any difference.

You should see some difference. If there is no difference then I don't know what's going on because when you see this log, the library is about to resolve and finish executing and return that json to AWS.

ml27299 commented 1 year ago

so i logged what the response looks like when i explicitly set the /robos.txt (which correctly shows the robots.txt) this is what I get for SERVERLESS_EXPRESS:FORWARD_RESPONSE:EVENT_SOURCE_RESPONSE

{
  message: 'SERVERLESS_EXPRESS:FORWARD_RESPONSE:EVENT_SOURCE_RESPONSE',
  successResponse: '{\n' +
    '  statusCode: 200,\n' +
    "  body: 'VXNlci1BZ2VudDogT21uaUV4cGxvcmVyX0JvdApEaXNhbGxvdzogLwoKVXNlci1hZ2VudDogKgpEaXNhbGxvdzogL3Byb3MKRGlzYWxsb3c6IC9jbGllbnRzLwpEaXNhbGxvdzogL2NsaWVudApEaXNhbGxvdzogL2FkbWluLwpEaXNhbGxvdzogL2xvZ291dApEaXNhbGxvdzogL2xlYWQKRGlzYWxsb3c6IC91cGRhdGUKRGlzYWxsb3c6IC9xdW90ZQpEaXNhbGxvdzogL3NpZ251cApEaXNhbGxvdzogL3NlYXJjaApEaXNhbGxvdzogL3NlYXJjaC1tYXAKRGlzYWxsb3c6IC9pbnZpdGUKRGlzYWxsb3c6IC9yZXF1ZXN0CkRpc2FsbG93OiAvd2VsY29tZQpEaXNhbGxvdzogL3Byby9zaWdudXAKRGlzYWxsb3c6IC9hdXRoCkRpc2FsbG93OiAvcHJvdmlkZXJzCkRpc2FsbG93OiAvcHJvL25leHQvc3RlcApEaXNhbGxvdzogL2NoYXQKRGlzYWxsb3c6IC9jaGVjawpEaXNhbGxvdzogL2Nkbi1jZ2kvY2hhbGxlbmdlLXBsYXRmb3JtLwpEaXNhbGxvdzogL2Nkbi1jZ2kvYm0vY3Yv',\n" +
    '  headers: {\n' +
    "    'x-dns-prefetch-control': 'off',\n" +
    "    'x-frame-options': 'SAMEORIGIN',\n" +
    "    'strict-transport-security': 'max-age=15552000; includeSubDomains',\n" +
    "    'x-download-options': 'noopen',\n" +
    "    'x-content-type-options': 'nosniff',\n" +
    "    'x-xss-protection': '1; mode=block',\n" +
    "    'referrer-policy': 'same-origin',\n" +
    "    'surrogate-control': 'no-store',\n" +
    "    'cache-control': 'no-store, no-cache, must-revalidate, proxy-revalidate',\n" +
    "    pragma: 'no-cache',\n" +
    "    expires: '0',\n" +
    "    'accept-ranges': 'bytes',\n" +
    "    'last-modified': 'Tue, 01 Jan 1980 00:00:00 GMT',\n" +
    "    'content-type': 'text/plain; charset=utf-8',\n" +
    "    'content-length': '492',\n" +
    `    etag: 'W/"1ec-3hr94AzacoVTnRZWk4ONFnLuyq8"'\n` +
    '  },\n' +
    '  multiValueHeaders: undefined,\n' +
    '  isBase64Encoded: true\n' +
    '}',
  body: '[BASE64_ENCODED]'
}

the only difference I see is that the "charset" on the content-type is "utf-8" vs "UTF-8"

H4ad commented 1 year ago

In that case, now you get this raw response and just return it in the lambda, erase all the code and leave it like this:

export const index = () => {
  return YOUR_JSON;
}

And try with both options. If it works, well, we have a problem with the library. If it doesn't work, ALB doesn't like UTF-8 ahhahaah.

ml27299 commented 1 year ago

ok, so i did what you suggested, interestingly enough, it works with both utf-8 and UTF-8

export const handler = async () => {
    return {
        statusCode: 200,
        body: "VXNlci1BZ2VudDogT21uaUV4cGxvcmVyX0JvdApEaXNhbGxvdzogLwoKVXNlci1hZ2VudDogKgpEaXNhbGxvdzogL3Byb3MKRGlzYWxsb3c6IC9jbGllbnRzLwpEaXNhbGxvdzogL2NsaWVudApEaXNhbGxvdzogL2FkbWluLwpEaXNhbGxvdzogL2xvZ291dApEaXNhbGxvdzogL2xlYWQKRGlzYWxsb3c6IC91cGRhdGUKRGlzYWxsb3c6IC9xdW90ZQpEaXNhbGxvdzogL3NpZ251cApEaXNhbGxvdzogL3NlYXJjaApEaXNhbGxvdzogL3NlYXJjaC1tYXAKRGlzYWxsb3c6IC9pbnZpdGUKRGlzYWxsb3c6IC9yZXF1ZXN0CkRpc2FsbG93OiAvd2VsY29tZQpEaXNhbGxvdzogL3Byby9zaWdudXAKRGlzYWxsb3c6IC9hdXRoCkRpc2FsbG93OiAvcHJvdmlkZXJzCkRpc2FsbG93OiAvcHJvL25leHQvc3RlcApEaXNhbGxvdzogL2NoYXQKRGlzYWxsb3c6IC9jaGVjawpEaXNhbGxvdzogL2Nkbi1jZ2kvY2hhbGxlbmdlLXBsYXRmb3JtLwpEaXNhbGxvdzogL2Nkbi1jZ2kvYm0vY3Yv",
        headers: {
            "x-dns-prefetch-control": "off",
            "x-frame-options": "SAMEORIGIN",
            "strict-transport-security": "max-age=15552000; includeSubDomains",
            "x-download-options": "noopen",
            "x-content-type-options": "nosniff",
            "x-xss-protection": "1; mode=block",
            "referrer-policy": "same-origin",
            "surrogate-control": "no-store",
            "cache-control": "no-store, no-cache, must-revalidate, proxy-revalidate",
            "pragma": "no-cache",
            "expires": "0",
            "accept-ranges": "bytes",
            "last-modified": "Tue, 01 Jan 1980 00:00:00 GMT",
            "content-type": "text/plain; charset=UTF-8",
            "content-length": "492",
            "etag": 'W/"1ec-3hr94AzacoVTnRZWk4ONFnLuyq8"',
        },
        multiValueHeaders: undefined,
        isBase64Encoded: true,
    };
};

and

export const handler = async () => {
    return {
        statusCode: 200,
        body: "VXNlci1BZ2VudDogT21uaUV4cGxvcmVyX0JvdApEaXNhbGxvdzogLwoKVXNlci1hZ2VudDogKgpEaXNhbGxvdzogL3Byb3MKRGlzYWxsb3c6IC9jbGllbnRzLwpEaXNhbGxvdzogL2NsaWVudApEaXNhbGxvdzogL2FkbWluLwpEaXNhbGxvdzogL2xvZ291dApEaXNhbGxvdzogL2xlYWQKRGlzYWxsb3c6IC91cGRhdGUKRGlzYWxsb3c6IC9xdW90ZQpEaXNhbGxvdzogL3NpZ251cApEaXNhbGxvdzogL3NlYXJjaApEaXNhbGxvdzogL3NlYXJjaC1tYXAKRGlzYWxsb3c6IC9pbnZpdGUKRGlzYWxsb3c6IC9yZXF1ZXN0CkRpc2FsbG93OiAvd2VsY29tZQpEaXNhbGxvdzogL3Byby9zaWdudXAKRGlzYWxsb3c6IC9hdXRoCkRpc2FsbG93OiAvcHJvdmlkZXJzCkRpc2FsbG93OiAvcHJvL25leHQvc3RlcApEaXNhbGxvdzogL2NoYXQKRGlzYWxsb3c6IC9jaGVjawpEaXNhbGxvdzogL2Nkbi1jZ2kvY2hhbGxlbmdlLXBsYXRmb3JtLwpEaXNhbGxvdzogL2Nkbi1jZ2kvYm0vY3Yv",
        headers: {
            "x-dns-prefetch-control": "off",
            "x-frame-options": "SAMEORIGIN",
            "strict-transport-security": "max-age=15552000; includeSubDomains",
            "x-download-options": "noopen",
            "x-content-type-options": "nosniff",
            "x-xss-protection": "1; mode=block",
            "referrer-policy": "same-origin",
            "surrogate-control": "no-store",
            "cache-control": "no-store, no-cache, must-revalidate, proxy-revalidate",
            "pragma": "no-cache",
            "expires": "0",
            "accept-ranges": "bytes",
            "last-modified": "Tue, 01 Jan 1980 00:00:00 GMT",
            "content-type": "text/plain; charset=utf-8",
            "content-length": "492",
            "etag": 'W/"1ec-3hr94AzacoVTnRZWk4ONFnLuyq8"',
        },
        multiValueHeaders: undefined,
        isBase64Encoded: true,
    };
};

I confirmed in the browser inspector that the response headers reflects the code's response

H4ad commented 1 year ago

@ml27299 Well, I'm happy to announce that you've discovered a strange bug, achievement unlocking!

Also dude I really don't know what the problem you are having with the 502, if both answers are the same and the framework is resolving and returning, the only thing I could think of is callbackWaitsForEmptyEventLoop, you changed this configuration somehow? In this case in the library it is resolution mode.

ml27299 commented 1 year ago

Haha, I hate that award. Ya, issue doesnt really make sense to me either, everything seems to be setup correct. No, I didnt mess with callbackWaitsForEmptyEventLoop

const binaryMimeTypes = [
    "application/javascript",
    "application/json",
    "application/octet-stream",
    "application/xml",
    "font/eot",
    "font/opentype",
    "font/otf",
    "image/jpeg",
    "image/png",
    "image/svg+xml",
    "text/comma-separated-values",
    "text/css",
    "text/html",
    "text/javascript",
    "text/plain",
    "text/text",
    "text/xml",
];

export const handler = serverlessExpress({
    app,
    logSettings: {
        level: ENV !== "production" ? "debug" : "error",
    },
    respondWithErrors: ENV !== "production",
    binarySettings: { contentTypes: binaryMimeTypes },
});

Thats my confg - luckily I dont serve compiled js assets via a static public path within the app, so I have very limited number of files I'd put there, robots.txt being it for now. Anyways, thx for guiding me thru this weird issue

H4ad commented 1 year ago

@ml27299 It looks like the handler is fine, well let's keep this issue open and if we see another one with this issue we can try to dig deeper to find the root cause of this bug.

Wafel commented 1 year ago

I met a very similar issue and in my case it was a type of content-length header. express.static() produces content-length as a number, but apparently ALB requires headers as strings. As a workaround I have built wrapper around the lambda handler to stringify all headers.

ml27299 commented 1 year ago

yep, that worked! Idk if it'd be better for vendia/serverless-express to stringify all the headers by default or allow a custom function to be passed in to edit the headers, but at the very least mentioning it in the docs.

wrapper

const serverlessExpress = require("@vendia/serverless-express");
let serverlessExpressInstance;

export const handler = async (event, context) => {
    function stringifyHeader(response) {
        for (let key in response.headers) {
            response.headers[key] = response.headers[key].toString();
        }
        return response;
    }

    if (serverlessExpressInstance) {
        const response = await serverlessExpressInstance(event, context);
        return stringifyHeader(response);
    }

    serverlessExpressInstance = serverlessExpress({
        app
    });

    const response = await serverlessExpressInstance(event, context);
    return stringifyHeader(response);
};