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,
}
function(event, context)toreturn function(event, context)in thelambda_limit_per_dayfunction. At present, you're not returning anything in those two paths so the return value is implicitly None and that's a malformed response, causing 502.