dougmoscrop / serverless-http

Use your existing middleware framework (e.g. Express, Koa) in AWS Lambda 🎉
Other
1.74k stars 167 forks source link

Running serverless express behind AWS ALB/target group #132

Open peebles opened 4 years ago

peebles commented 4 years ago

I had a real hard time moving my function from behind API Gateway to behind an ALB with a target group. Using the ALB has some nice advantages (like routing) and it doesn't have the 30 second timeout that AGW does. But ... the event structure is slightly different! I thought about doing a pull request, but the nice architecture of this module makes it possible to deal with these differences with configuration. So it is a matter of documentation rather than making code changes. Here is my handler code that works behind an ALB:

'use strict';
const serverless = require('serverless-http');
const app = require('./server');

module.exports.handler = (event, context) => {
  // When lambda is launched from an ALB/target group, the incoming event is a little
  // different than if it came from API Gateway.  In particular, the query params are
  // NOT url decoded like they are on AGW.
  //
  // Make sure you do NOT enable multi-value-headers on the target group that launches this
  // lambda function.  If you must, then you must also deal with event.multiValueHeaders
  // and you must add multiValueHeaders to the outgoing response structure.  See this article
  // for more detail: https://serverless-training.com/articles/api-gateway-vs-application-load-balancer-technical-details/
  //
  if ( event.queryStringParameters ) {
    let params = {};
    event.queryStringParameters = Object.keys(event.queryStringParameters).forEach((k) => {
      params[decodeURIComponent(k)] = decodeURIComponent(event.queryStringParameters[k]);
    });
    event.queryStringParameters = params;
  }
  const f = serverless(app, {
    request(request, event, context) {
      // I think bodyParser does something incorrect if the body is a Buffer with a length of zero,
      // which is how serverless-http deals with the body if there is no body.  bodyParser leave the
      // Buffer alone, and then downstream Loopback 3 code goes bonkers if the body is a Buffer.
      if ( Buffer.isBuffer(request.body) && Buffer.byteLength(request.body) === 0 )
        request.body = ''; // Fix for Loopback 3
    }
  });
  return f(event, context);
}

Feel free to incorporate this somewhere in the doc, if you think it is worth it.

jheising commented 3 years ago

Ran into this same problem myself and thanks much for confirming that this is an issue!

Quick thought: if AWS does "fix" this in the future, or if you want to be able to switch the code to run between ALB and API Gateway, I think you'll run into issues decoding an already decoded URL parameter. For example if the decoded value was something like A deal at %23 off, then it would be improperly decoded to A deal at # off if you decided to switch to API-G in the future (sort of a bad example, but you get it). Probably not a lot of people switching on a daily basis, but this would be important for any sort of distributed library.

Seems like maybe we need an if statement to determine if the event is coming from ALB vs. API Gateway? This would solve the ALB vs. APIG issue, but wouldn't necessarily solve the problem if they "fix" or change this in the future. Any other creative ideas thoughts? As far as I know, I can't think of a way to automatically determine if a string has already been urldecoded (because the actual value of the parameter might have characters that look like urlencoded chars), but who knows, maybe I'm missing something.

dougmoscrop commented 3 years ago

Two things:

dougmoscrop commented 3 years ago

Looks like the event payload might have:

requestContext': {
'elb': {
'targetGroupArn': 'arn:aws:elasticloadbalancing:us-east-1:XXXXXXXXXXX:targetgroup/sample/6d0ecf831eec9f09'
}

Which could be a hint

jheising commented 3 years ago

Looks like the event payload might have:

requestContext': {
'elb': {
'targetGroupArn': 'arn:aws:elasticloadbalancing:us-east-1:XXXXXXXXXXX:targetgroup/sample/6d0ecf831eec9f09'
}

Which could be a hint

Brilliant! Yeah I thought maybe there would be a special/custom header that might give it away, but I didn't think about checking the rest of the event payload. Happy to submit a pull-request if you want.

jheising commented 3 years ago

PR #190 submitted.