Spent more than half a day, struggled with this error when I was testing an async lambda on my local.
Error
I created a lambda which had an async implementation, but when I deployed it on lambda, and used function url to access it.
It started giving an error in response
{"__type": "InternalError", "message": "exception while calling lambda with unknown operation: 'str' object has no attribute 'get'"}
Steps to recreate
Let us create a lambda with simple wait function.
export const sleep = (ms) => {
return new Promise(resolve => setTimeout(resolve, ms));
}
exports.handler = async (event, context) => {
await sleep(10)
return {
statusCode: 200,
headers: {'Content-Type': 'application/json'},
body: "Hello World",
};
};
Deploying to localstack
So we have our code ready to deploy, I will use terraform to deploy the code to localstack. But first make sure, you have your localstack setup, follow the instructions if you have not.
Terraform
Now we will create provider.tf
to point our aws provider to localstack.
terraform {
required_version = "~> 1.3.3"
}
provider "aws" {
access_key = "test"
secret_key = "test"
region = "us-east-1"
s3_force_path_style = false
skip_credentials_validation = true
skip_metadata_api_check = true
skip_requesting_account_id = true
endpoints {
apigateway = "http://localhost:4566"
apigatewayv2 = "http://localhost:4566"
cloudformation = "http://localhost:4566"
cloudwatch = "http://localhost:4566"
dynamodb = "http://localhost:4566"
ec2 = "http://localhost:4566"
es = "http://localhost:4566"
elasticache = "http://localhost:4566"
firehose = "http://localhost:4566"
iam = "http://localhost:4566"
kinesis = "http://localhost:4566"
lambda = "http://localhost:4566"
rds = "http://localhost:4566"
redshift = "http://localhost:4566"
route53 = "http://localhost:4566"
s3 = "http://s3.localhost.localstack.cloud:4566"
secretsmanager = "http://localhost:4566"
ses = "http://localhost:4566"
sns = "http://localhost:4566"
sqs = "http://localhost:4566"
ssm = "http://localhost:4566"
stepfunctions = "http://localhost:4566"
sts = "http://localhost:4566"
}
}
As we have the provider setup, lets deploy the lambda.
resource "aws_iam_role" "iam_for_lambda_fix" {
name = "iam_for_lambda_fix"
assume_role_policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "sts:AssumeRole",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Effect": "Allow",
"Sid": ""
}
]
}
EOF
}
data "archive_file" "lambda_zip" {
type = "zip"
source_file = "./index.js"
output_path = "lambda.zip"
}
resource "aws_lambda_function" "lambda" {
filename = "lambda.zip"
function_name = "lambda_fix"
role = aws_iam_role.iam_for_lambda_fix.arn
handler = "./index.handler"
source_code_hash = "${filebase64sha256("./index.js")}"
runtime = "nodejs16.x"
timeout = 30
depends_on = [data.archive_file.lambda_zip]
}
resource "aws_lambda_function_url" "function_url" {
function_name = aws_lambda_function.lambda.function_name
authorization_type = "NONE"
}
output "url" {
value = aws_lambda_function_url.function_url
}
Here, we have created an iam policy, then archived the js file, and deploy a function.
All set, now to run this application.
$ terraform init
$ terraform plan
$ terraform apply --auto-approve
Once the commands are executed.
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
url = {
"authorization_type" = "NONE"
"cors" = tolist([])
"function_arn" = "arn:aws:lambda:us-east-1:000000000000:function:lambda_fix"
"function_name" = "lambda_fix"
"function_url" = "http://cdd7e6500eb91965c99dc0eb42b1613a.lambda-url.us-east-1.localhost.localstack.cloud:4566/"
"id" = "lambda_fix"
"qualifier" = ""
"timeouts" = null /* object */
"url_id" = "cdd7e6500eb91965c99dc0eb42b1613a"
}
now if we do curl, we will get the error
$ curl http://cdd7e6500eb91965c99dc0eb42b1613a.lambda-url.us-east-1.localhost.localstack.cloud:4566/
{"__type": "InternalError", "message": "exception while calling lambda with unknown operation: the JSON object must be str, bytes or bytearray, not Response"}%
But if you deploy the same lambda to aws , it works fine. To my surprise if I remove the async operation, the application works fine on localstack as well.
Fix
The fix is rather simple, for unknown reason, localstack is unable to convert the async/await to promise result.
So to fix the above issue lets do a simple modification.
export const sleep = (ms) => {
return new Promise(resolve => setTimeout(resolve, ms));
}
exports.handler = async (event, context) => {
await sleep(10)
return Promise.resolve({
statusCode: 200,
headers: {'Content-Type': 'application/json'},
body: "Hello World",
});
};
We just add the Promise.resolve() explicitly. And that solve the issue with localstack function url
Apply our change again using terraform apply
& finally do a curl.
$ curl http://cdd7e6500eb91965c99dc0eb42b1613a.lambda-url.us-east-1.localhost.localstack.cloud:4566/
Hello World
You can find the code here.
If you liked this article, you can buy me a coffee
Leave a comment