amazon web services – Calling AWS Lambda via api gateway on localstack doesn’t work as expected

Hi I have downloaded the most basic java lambda example

package helloworld;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyRequestEvent;
import com.amazonaws.services.lambda.runtime.events.APIGatewayProxyResponseEvent;

/**
 * Handler for requests to Lambda function.
 */
public class App implements RequestHandler<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent> {

    public APIGatewayProxyResponseEvent handleRequest(final APIGatewayProxyRequestEvent input, final Context context) {
        Map<String, String> headers = new HashMap<>();
        headers.put("Content-Type", "application/json");
        headers.put("X-Custom-Header", "application/json");

        APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent()
                .withHeaders(headers);
        try {
            final String pageContents = this.getPageContents("https://checkip.amazonaws.com");
            String output = String.format("{ "message": "hello world", "location": "%s" }", pageContents);

            return response
                    .withStatusCode(200)
                    .withBody(output);
        } catch (IOException e) {
            return response
                    .withBody("{}")
                    .withStatusCode(500);
        }
    }

    private String getPageContents(String address) throws IOException{
        URL url = new URL(address);
        try(BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()))) {
            return br.lines().collect(Collectors.joining(System.lineSeparator()));
        }
    }
}


I then create a docker image with commands

mvn compile dependency:copy-dependencies -DincludeScope=runtime
cd ..
docker build -t $DOCKER_TAG .
docker push $DOCKER_TAG

From then I have the following terraforms files

# Create the lambda role (using lambdarole.json file)
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

resource "aws_iam_role" "lambda_role" {
  name               = "${var.stack_prefix}-role-lambdarole-${var.unique_name}"
  assume_role_policy = file("${path.module}/files/lambdarole.json")
}

# Apply the Policy Document we just created
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

resource "aws_iam_role_policy" "lambda_policy" {
  name   = "${var.stack_prefix}-role-lambdapolicy-${var.unique_name}"
  role   = aws_iam_role.lambda_role.id
  policy = file("${path.module}/files/lambdapolicy.json")
}


#  Variables
variable "apiName" { default = "java-lambda" }

# variable "accountId" {}

# API Gateway


resource "aws_lambda_function" "test_lambda" {
  function_name = "${var.apiName}"
  image_uri     = "localhost:4510/ac.uk-jlambda:1.0.0"
  role          = aws_iam_role.lambda_role.arn
  runtime       = "java11"
  handler       = "example.FunctionConfiguration::Function"
  timeout       = "60"
  publish       = true
}


resource "aws_api_gateway_rest_api" "apiLambda" {
  name = "${var.apiName}"
}



resource "aws_api_gateway_resource" "proxy" {
  rest_api_id = aws_api_gateway_rest_api.apiLambda.id
  parent_id   = aws_api_gateway_rest_api.apiLambda.root_resource_id
  path_part   = "{proxy+}"
}

resource "aws_api_gateway_method" "proxyMethod" {
  rest_api_id   = aws_api_gateway_rest_api.apiLambda.id
  resource_id   = aws_api_gateway_resource.proxy.id
  http_method   = "ANY"
  authorization = "NONE"
}

resource "aws_api_gateway_integration" "lambda" {
  rest_api_id = aws_api_gateway_rest_api.apiLambda.id
  resource_id = aws_api_gateway_method.proxyMethod.resource_id
  http_method = aws_api_gateway_method.proxyMethod.http_method

  integration_http_method = "GET"
  type                    = "AWS_PROXY"
  uri                     = aws_lambda_function.test_lambda.invoke_arn
}




resource "aws_api_gateway_method" "proxy_root" {
  rest_api_id   = aws_api_gateway_rest_api.apiLambda.id
  resource_id   = aws_api_gateway_rest_api.apiLambda.root_resource_id
  http_method   = "ANY"
  authorization = "NONE"
}

resource "aws_api_gateway_integration" "lambda_root" {
  rest_api_id = aws_api_gateway_rest_api.apiLambda.id
  resource_id = aws_api_gateway_method.proxy_root.resource_id
  http_method = aws_api_gateway_method.proxy_root.http_method

  integration_http_method = "POST"
  type                    = "AWS_PROXY"
  uri                     = aws_lambda_function.test_lambda.invoke_arn
}


resource "aws_api_gateway_deployment" "apideploy" {
  depends_on = [
    aws_api_gateway_integration.lambda,
    aws_api_gateway_integration.lambda_root,
  ]

  rest_api_id = aws_api_gateway_rest_api.apiLambda.id
  stage_name  = "test"
}


resource "aws_lambda_permission" "apigw" {
  statement_id  = "AllowAPIGatewayInvoke"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.test_lambda.function_name
  principal     = "apigateway.amazonaws.com"

  # The "/*/*" portion grants access from any method on any resource
  # within the API Gateway REST API.
  source_arn = "${aws_api_gateway_rest_api.apiLambda.execution_arn}/*/*"
}


output "base_url" {
  value = aws_api_gateway_deployment.apideploy.invoke_url
}

Then I call terraform init, plan and apply. Afterwards when I use the command:

awslocal --region=eu-west-2 lambda invoke --function-name java-lambda --payload '{"key1":"hello world!","key2":"hello another world!"}' --cli-binary-format raw-in-base64-out /dev/stdout

I get output

{"body":"{"message": "Go Serverless v2.0! Your function executed successfully!", "input": {"key1": "hello world!", "key2": "hello another world!"}}","statusCode":200}
{
    "StatusCode": 200,
    "LogResult": "",
    "ExecutedVersion": "$LATEST"
}

Then I use this command to find the id of the api gateway

awslocal apigateway get-rest-apis --region eu-west-2

and use this id in the following url format

http://localhost:4566/restapis/ngabak60o4/dev/_user_request_/

This shows a json response in the browser

{"message": "Go Serverless v2.0! Your function executed successfully!", "input": {"body": "", "headers": {"Authorization": "", "Connection": "keep-alive", "Cookie": "aekt_hostname=ucl-3.ombiel.co.uk; a=96df33c3b846ea04YD9uLTvub3R3%2FxtuaEssxWBymFrbu7Vg5LH%2BpWGOQJvXpE9FywQ4OnoISm8ccMID0yeoi1EnhlDcau66bNXuh1k6PHi317kCOJr3%2B7Qus8vEYkw%2FCs7fwS%2Bmd5%2B7ZvwveqgUgTtYbdlsw1Jn6ozcbDbmw3oqhXt1r3%2Fi%2F9A5I3P9gHHYwyfXjLMHWjqZFmYJ7elQCSgTCPeSkuQW%2BbRGNw3XfqSuCvd8%2BEjQCqApjQ9C3Yb3RX9DcueKaIfjntKCXbxyyyhgdNCI7bYoQuqAOdZpmZs0pygabE%2B1GuDfRTbkJ%2BbLRaSL8eYjUAmrzfKP; __a=471ffc53886568546223239137b5ac60; ombl_web2=yes; ombl_device_grade=med; campusM[7]=8%5B; __utmz=111872281.1650902813.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __utma=111872281.1289308641.1650902813.1652888907.1655293849.25", "Host": "localhost:4566", "Remote-Addr": "172.17.0.1", "Sec-Ch-Ua": "" Not A;Brand";v="99", "Chromium";v="102", "Google Chrome";v="102"", "Sec-Ch-Ua-Mobile": "?0", "Sec-Ch-Ua-Platform": ""Windows"", "Sec-Fetch-Dest": "document", "Sec-Fetch-Mode": "navigate", "Sec-Fetch-Site": "none", "Sec-Fetch-User": "?1", "Upgrade-Insecure-Requests": "1", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36", "X-Forwarded-For": "172.17.0.1, localhost:4566, 127.0.0.1, localhost:4566", "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, br", "accept-language": "en-GB,en-US;q=0.9,en;q=0.8", "x-localstack-edge": "http://localhost:4566", "x-localstack-tgt-api": "apigateway"}, "httpMethod": "GET", "isBase64Encoded": false, "multiValueHeaders": {"Authorization": [""], "Connection": ["keep-alive"], "Cookie": ["aekt_hostname=ucl-3.ombiel.co.uk; a=96df33c3b846ea04YD9uLTvub3R3%2FxtuaEssxWBymFrbu7Vg5LH%2BpWGOQJvXpE9FywQ4OnoISm8ccMID0yeoi1EnhlDcau66bNXuh1k6PHi317kCOJr3%2B7Qus8vEYkw%2FCs7fwS%2Bmd5%2B7ZvwveqgUgTtYbdlsw1Jn6ozcbDbmw3oqhXt1r3%2Fi%2F9A5I3P9gHHYwyfXjLMHWjqZFmYJ7elQCSgTCPeSkuQW%2BbRGNw3XfqSuCvd8%2BEjQCqApjQ9C3Yb3RX9DcueKaIfjntKCXbxyyyhgdNCI7bYoQuqAOdZpmZs0pygabE%2B1GuDfRTbkJ%2BbLRaSL8eYjUAmrzfKP; __a=471ffc53886568546223239137b5ac60; ombl_web2=yes; ombl_device_grade=med; campusM[7]=8%5B; __utmz=111872281.1650902813.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none); __utma=111872281.1289308641.1650902813.1652888907.1655293849.25"], "Host": ["localhost:4566"], "Remote-Addr": ["172.17.0.1"], "Sec-Ch-Ua": ["" Not A;Brand";v="99", "Chromium";v="102", "Google Chrome";v="102""], "Sec-Ch-Ua-Mobile": ["?0"], "Sec-Ch-Ua-Platform": [""Windows""], "Sec-Fetch-Dest": ["document"], "Sec-Fetch-Mode": ["navigate"], "Sec-Fetch-Site": ["none"], "Sec-Fetch-User": ["?1"], "Upgrade-Insecure-Requests": ["1"], "User-Agent": ["Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36"], "X-Forwarded-For": ["172.17.0.1, localhost:4566, 127.0.0.1, localhost:4566"], "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, br"], "accept-language": ["en-GB,en-US;q=0.9,en;q=0.8"], "x-localstack-edge": ["http://localhost:4566"], "x-localstack-tgt-api": ["apigateway"]}, "multiValueQueryStringParameters": null, "path": "/", "pathParameters": {}, "queryStringParameters": null, "requestContext": {"accountId": "000000000000", "apiId": "kkjc44n3w7", "authorizer": {}, "domainName": "localhost", "domainPrefix": "localhost", "httpMethod": "GET", "identity": {"accountId": "000000000000", "sourceIp": "127.0.0.1", "userAgent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36"}, "path": "/dev/", "protocol": "HTTP/1.1", "requestId": "4313b0d1-1db6-4d55-af5b-7d205965f2ce", "requestTime": "23/Jun/2022:12:51:49 +0000", "requestTimeEpoch": 1655988709945, "resourceId": "1gbuakz0ci", "resourcePath": "/", "stage": "dev"}, "resource": "/"}}

Which claims to be success. But I don’t see the output of my handleRequest method.

I follow a similar tutorial for node js and that works correctly.

Can anyone please see what I have done wrong here?

Many thanks

Leave a Comment