I am investigating the feasibility of using a python lambda to serve a throttled endpoint connecting to a dynamo database. There is a basic token authentication and the counts for the throttling are kept in a dynamodb with a TTL of a day. I call it throttling but its actualy a daily count of requests. All the complicated parts work and if the user is unauthenticated I get the expected response {"message": "No token, or your token is invalid!"}
and if the daily count of requests is exhausted i also get the expected message
{"message": "Daily Invocations Exhausted!"}
however when the authentication and throttle allows a response from the lambda_handler
i get a 502 {"message": "Internal server error"}
.
When I look at the API Gateway test i see this log:
Mon Nov 29 14:56:28 UTC 2021 : Endpoint response body before transformations: null
Mon Nov 29 14:56:28 UTC 2021 : Execution failed due to configuration error: Malformed Lambda proxy response
Mon Nov 29 14:56:28 UTC 2021 : Method completed with status: 502
And when I print the body and see the logs in CloudFormation i see this:
2021-11-29T15:56:28.103 01:00 {"message": "stuff!"}
I am new with python lambdas so I am completely at a loss here, this behaviour makes no sense to me, I am hoping you can see something in the code which I am missing.
import json
import time
# import requests
import boto3
from botocore.exceptions import ClientError
dynamodb = boto3.client('dynamodb')
DAY_LIMIT = 5
def get_token(token):
try:
response = dynamodb.get_item(TableName='tokens', Key={'token': {'S': token}})
except ClientError as e:
print(e.response['Error']['Message'])
else:
return None if 'Item' not in response else response['Item']
def get_token_throttle(token):
try:
response = dynamodb.get_item(TableName='token_throttle',
Key={'token': {'S': token}})
except ClientError as e:
print(e.response['Error']['Message'])
else:
return None if 'Item' not in response else response['Item']
def authenticated(function):
def wrapper(event, context):
if (event['queryStringParameters'] is None or "token" not in event['queryStringParameters'] or
get_token(event['queryStringParameters']['token']) is None):
return {
"statusCode": 401,
"body": json.dumps({
"message": "No token, or your token is invalid!"
}),
}
else:
return function(event, context)
return wrapper
def lambda_limit_per_day(function):
def wrapper(event, context):
throttle = get_token_throttle(event['queryStringParameters']['token'])
if throttle == None:
dynamodb.put_item(TableName='token_throttle', Item={
"token": {"S": event['queryStringParameters']['token']},
"invocations": {"N": str(1)},
"deletion": {"N": str(int(time.time()) 86400)} # one day
})
function(event, context)
elif int(throttle['invocations']['N']) < DAY_LIMIT:
dynamodb.put_item(TableName='token_throttle', Item={
"token": {"S": event['queryStringParameters']['token']},
"invocations": {"N": str(int(throttle['invocations']['N']) 1)},
"deletion": {"N": str(throttle['deletion']['N'])} # one day
})
function(event, context)
else:
return {
"statusCode": 200,
"body": json.dumps({
"message": "Daily Invocations Exhausted!"
}),
}
return wrapper
@authenticated
@lambda_limit_per_day
def lambda_handler(event, context):
"""Sample pure Lambda function
Parameters
----------
event: dict, required
API Gateway Lambda Proxy Input Format
Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format
context: object, required
Lambda Context runtime methods and attributes
Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html
Returns
------
API Gateway Lambda Proxy Output Format: dict
Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html
"""
print("Running Final function")
body = json.dumps({
"message": "stuff!"
})
print(body)
return {
"statusCode": 200,
"body": body,
}
CodePudding user response:
Your lambda_handler
function is decorated and has an associated decorator function named lambda_limit_per_day
. The return value of your Lambda function will therefore be the return value of the decorator's wrapper function.
At present, your wrapper function is not returning anything in the two paths where you handle 'not throttled' and 'within throttling limits' -- your code simply calls function(event, context)
but discards the return value from those calls (to the lambda_handler
decorated function). So, the return value of the wrapper function, and hence your decorated Lambda function, is implicitly None
and that's a malformed response, causing API Gateway to generate a 502 response.
Change those two instances of function(event, context)
to:
return function(event, context)