dougmoscrop / serverless-http

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

Lambda@Edge Support #79

Open spencerbeggs opened 5 years ago

spencerbeggs commented 5 years ago

Hi, Doug. Thank you for this fantastic module. I got it up and running a a couple projects where I use Lambda@Edge to serve HTML. This requires a small transform of the payload to conform to the Blacklisted and Read-Only Headers. Would you be interested in a PR for this use case, perhaps we could add a second argument to the exported serverless function to support vendor platforms, eg:

// vanilla Lambda@Edge handling
const handler = serverless(app, "edge");
// html Lambda@Edge handling
const handler = serverless(app, "edge-html");

Alternatively, I could publish separate modules that require this module as a dependency that perform the necessary transformations.

dougmoscrop commented 5 years ago

Sure, would love to support this, there already is a second argument for options - I had started to refactor this library to support different providers but I got sidetracked. how about:

serverless(app, {
    platform: 'aws-lambda' // or 'aws-lambda-edge', eventually also 'gcf', 'ibm', etc.
});

Or is there specific changes that have to happen for HTML vs other Lambda@Edge scenarios?

spencerbeggs commented 5 years ago

The base case for Lambda@Edge is that the output of the current implementation has to transform headers into AWS' format. This is what my handler looks like:

const zlib = require("zlib");
const serverless = require("serverless-http");
const app = require("./app");
const handler = serverless(app);

const readOnlyHeaders = [
  "accept-encoding",
  "content-length",
  "if-modified-since",
  "if-none-Match",
  "if-range",
  "if-unmodified-since",
  "range",
  "transfer-encoding",
  "via"
];

module.exports.handler = async (event, context, callback) => {
  const obj = await handler(event, context);
  const buffer = zlib.gzipSync(obj.body);
  const base64EncodedBody = buffer.toString("base64");
  callback(null, {
    status: obj.statusCode.toString(),
    statusDescription: "OK",
    bodyEncoding: "base64",
    headers: Object.keys(obj.headers).reduce(
      (acc, key) => {
        let normalizedKey = key.toLowerCase();
        if (!readOnlyHeaders.includes(normalizedKey)) {
          if (acc[normalizedKey]) {
            acc[normalizedKey].push({
              key: normalizedKey,
              value: obj.headers[key]
            });
          } else {
            acc[normalizedKey] = [
              {
                key: normalizedKey,
                value: obj.headers[key]
              }
            ];
          }
        }
        return acc;
      },
      { "content-encoding": [{ key: "content-encoding", value: "gzip" }] }
    ),
    body: base64EncodedBody
  });
};

That only does the Read-Only handlers and then does the HTML processing. Since there are different requirements for the type of function being called, maybe the API can look like:

serverless(app, {
    platform: 'aws-lambda-edge',
    type: 'origin-request'
});

We could then encapsulate the monekying with the header and detect HTML content-type and handle that automatically and the API can remain nice and clean like it is.

dougmoscrop commented 5 years ago

Okay, seems pretty straightforward! I don't know that I have a strong preference for the names of these properties, I had alternative thought of something like:

provider: aws type: lambda-edge-origin-request

i.e. default provider is aws, default type is lambda for aws, then gcp and ibm would be other providers, etc.

dougmoscrop commented 5 years ago

^ I don't think this is really going to change much of the impl, if you want to PR something for further discussion great, if not I can start re-introducing the multi-provider code and then you can build on it there. I won't be able to do that today though, maybe not this week either.

spencerbeggs commented 5 years ago

I started a two-week sprint today that requires this functionality, so I will fork and get to work.

wmertens commented 5 years ago

@spencerbeggs I see you instead forked aws-serverless-express-edge - was that a better fit? I also tried to use that, in the end running express in lambda seems like overkill, the http handling is already done by AWS. There's also modofunjs and now I just don't know what to choose.

spencerbeggs commented 5 years ago

I have a fork of that, too, but this package works better. You can find my fork here: https://github.com/spencerbeggs/serverless-http

I got the origin-request parsing to work, if you want to check it out. Getting this package to be multi-provider might incur a lot of overhead. In fact, getting this package to handle all the lambda proxy request types might be overkill as the routing stuff really only needs to be done on the origin request. Check out my package, I write a bunch of tests. And let me know what you think. I am still hacking on my fork as I am using it in an upcoming project.

dougmoscrop commented 5 years ago

I did some refactoring to make multi-provider support easier, so your fork is a bit behind!

I hope these changes make it easier for you to bring your changes in! See https://github.com/dougmoscrop/serverless-http/tree/master/lib/provider

EdisonHarada commented 5 years ago

I started implementing Lambda@Edge some hours ago using your latest commit. But I still need to implement tests and refactor my code.

I implemented in this simple application to get the server Ip and client Ip: https://d1lrl7frx0ubqe.cloudfront.net But I still need to figure out why it's getting the same ip for server and client, my bet is probably how the lambda@edge works, but I need to confirm it.

The original project is running in: https://koa-serverless.edisonharada.dev

If someone wants to check the changes my fork is here: https://github.com/EdisonHarada/serverless-http Also, I created a new provider to make the implementation easily for now and after we can check which approach would be better (and also the name for the provider haha), to use it I just changed:

const handler = serverlessHttp(app, {
  provider: 'awsEdge'
})
yshrsmz commented 4 years ago

Hi, any news regarding Lambda@Edge support? I recently started to develop an app for Lambda@Edge and found this issue. Would be great if this happens.

eelayoubi commented 3 years ago

Hey @dougmoscrop will this be merged? I imagine that it can be used for cloudfront -> Lambda Edge origin request event, right?

dougmoscrop commented 3 years ago

This is an issue not a PR - I will merge a PR that adds this support, absolutely

eelayoubi commented 3 years ago

@dougmoscrop ah yes, I was looking at the fork that has the edge support, and asking the question here 🙄. Thanks for the reply and sorry for the confusion 🙏. Do you have a timeline for when this will be done?

dougmoscrop commented 3 years ago

Oh it's been a while! I don't know if anyone is working on it, if you have the cycles to pick it up from that fork that would be great

eelayoubi commented 3 years ago

@dougmoscrop here is a PR https://github.com/dougmoscrop/serverless-http/pull/192 that includes lambda edge origin request. However, I did not have time to add Unit Tests for it.

lolJS commented 3 years ago

I've continued this effort on #196 which addresses some of the issues that @ljwagerfield brought up in the PR review.

bfischer1121 commented 1 year ago

hi any update of whether #196 will be merged?

logusgraphics commented 1 year ago

For anyone interested, the following library achieves lambda@edge integration seamlessly with singleton accessor to the event object. https://github.com/vendia/serverless-express/tree/mainline/examples/lambda-edge