honojs/hono

`headers` and `multiValueHeaders` are not set properly when using `hono/aws-lambda` with ALB

Closed this issue · 7 comments

yiss commented

What version of Hono are you using?

^4.3.2

What runtime/platform is your app running on?

Node, AWS Lambda

What steps can reproduce the bug?

When using hono/aws-lambdaadapter with a Lambda that has an ALB in front of it. It shouldn't use the multiValueHeaders as default, as this feature is not default when using ALB with Lambda target. Moreover, the set-cookie behavior in the adapter might result in bugs in some cases.

Example :

import { handle } from "hono/aws-lambda";
import { getCookie, setCookie } from "hono/cookie";
import { Hono } from "hono";

const app = new Hono();

app.get("/set-headers", (ctx) => {
  setCookie(ctx, "foo", "bar", {
    secure: process.env.NODE_ENV === "production",
    domain: `.${process.env.COOKIE_DOMAINE}`,
    httpOnly: true,
    maxAge: 60 * 10,
    sameSite: "Lax",
  });
  setCookie(ctx, "fizz", "buzz", {
    secure: process.env.NODE_ENV === "production",
    domain: `.${process.env.COOKIE_DOMAINE}`,
    httpOnly: true,
    maxAge: 60 * 10,
    sameSite: "Lax",
  });
  return ctx.redirect("/get-headers");
});

app.get("/get-headers", (ctx) => {
  const fooCookie = getCookie(ctx, "foo");
  const fizzCookie = getCookie(ctx, "fizz");
  return ctx.json({
    fooCookie,
    fizzCookie,
  });
});

export const handler = handle(app);

And CDK minimal config :

import * as cdk from '@aws-cdk/core';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as lambda from '@aws-cdk/aws-lambda';
import * as lambdaNodejs from '@aws-cdk/aws-lambda-nodejs';
import * as elbv2 from '@aws-cdk/aws-elasticloadbalancingv2';

export class CdkAlbLambdaStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // Create VPC
    const vpc = new ec2.Vpc(this, 'MyVpc', {
      maxAzs: 2
    });

    // Create Lambda function
    const lambdaFunction = new lambdaNodejs.NodejsFunction(this, 'MyLambda', {
      entry: 'lambda/index.ts', // Entry file for your Lambda function
      handler: 'handler', // Exported function name in your entry file
    });
   // Target Groupe

  const targetGroup = new elbv2.ApplicationTargetGroup(
        this,
        `${name}-target-group`,
        {
          vpc: this.alb.vpc,
          targetType: elbv2.TargetType.LAMBDA,
  
          targets: [new elbv2Targets.LambdaTarget(lambdaFunction)],
        }
      );


    // Create ALB
    const alb = new elbv2.ApplicationLoadBalancer(this, 'MyAlb', {
      vpc,
      internetFacing: true
    });
    const albCert = new acm.Certificate(this, "alb-cert", {
      domainName: `${hostedZoneName}`,
      validation: acm.CertificateValidation.fromDns(hostedZone),
    });

    const listener = alb.addListener("alb-http-listener", {
      port: 80,
      open: true,
    });

    // Create ALB Listener

    // Add Lambda as a target to ALB Listener
    listener.addTargets('LambdaTarget', {
      port: 80,
      targets: [targetGroup],
    });
  }
}

As I mentioned before the multiValueHeaders is not enabled by default, to enable it we must add the following

...
targetGroup.setAttribute("lambda.multi_value_headers.enabled", "true");
...

But when this behavior is enabled, the ALB will ignore the headers value and only take multiHeadersValue except in Hono adapter not all the headers are propagated from headers to multiValueHeaders. Disabling the multiValueHeaders will result in set-cookie being passed as an array so the ALB will only take the last value

Here is the result payload of the example with multiValueHeaders enabled :

{
    "result": {
        "body": "",
        "headers": {
            "access-control-allow-origin": "*",
            "content-type": "text/plain;charset=UTF-8",
            "location": "/get-headers"
        },
        "statusCode": 302,
        "isBase64Encoded": false,
        "multiValueHeaders": {
            "set-cookie": [
                "foo=bar; Max-Age=600; Domain=.mydomaine.com; Path=/; HttpOnly; SameSite=Lax",
                "fizz=buzz; Max-Age=600; Domain=.mydomaine.com; Path=/; HttpOnly; SameSite=Lax"
            ]
        }
    }
}

More info from AWS website : https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#multi-value-headers

Screenshot 2024-05-06 at 20 00 17

What is the expected behavior?

Two solution are possible :

  • Soluion 1: When the set-cookie is available, and it's an array, flatten it and put it directly in the event.cookies value as string
  • Solution 2: When multiValueHeaders is present, pass to it all the values from headers as well

What do you see instead?

No response

Additional information

I would love to contribute and create a PR for the fix. I have managed to create a wrapper around handle function for internal testing purposes.

Hi @yiss

Now #2585 is merged. Is this problem still occurring with it?

yiss commented

@yusukebe I haven't seen this change. I'm going to test it and report back

yiss commented

Yes I can confirm that it's still happening. It seems that ALB straight out ignores the headers if multiValueHeaders is present in the response

@yiss

Thank you for confirming. Can you create a PR to fix it?

yiss commented

@yusukebe I will raise the PR soon. I was thinking of adding an albProcessor to seperate the ALB behavior from the API Gateway for the multiHeadersValue. Any thoughts? Or should I just put the the change in createResult and createRequest ?

@yiss Thank you.

I was thinking of adding an albProcessor to seperate the ALB behavior from the API Gateway for the multiHeadersValue. Any thoughts? Or should I just put the the change in createResult and createRequest ?

@watany-dev Do you have any advance? ^

This approach looks good.

adding an albProcessor to seperate the ALB behavior from the API Gateway for the multiHeadersValue.