I wrote a simple python test, that calls a lambda behind an API gateway in a loop.
for doc in docs:
payload = json.dumps({})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", f"https://XXXXXXXX.execute-api.eu-central-1.amazonaws.com/docs", headers=headers, data=payload)
I expect that the "Concurrent executions" monitor shows a constant value "1". But what I get is sometimes a "2":
Does anyone know why? Does AWS Cloudwatch only query the difference between "START" and "END" logs in a specific timeframe?
Related
Im trying to create a simple API gateway in which, with a POST method to a certain endpoint, a lambda function is executed.
Setting that up was easy enough, but I'm having some trouble with the request/response handling. Im sending the following request to the API Gateway (Im using python 3.7).
payload = {
"data": "something",
"data2": "sometsadas"
}
response = requests.post('https://endpoint.com/test', params = payload)
That endpoint activates a lambda function when accesed. That function just returns the same event it received.
import json
def lambda_handler(event, context):
# TODO implement
return event
How can I make it so the return value of my lambda function is actually the response from the request? (Or at least a way in which the return value can be found somewhere inside the response)
Seems it was a problem with how the information is sent, json format is required. Solved it by doing the following in the code.
payload{'data': 'someData'}
config_response = requests.post(endpointURL, data = json.dumps(config_payload))
I'm uploading an image to s3, through a lambda, and everything works well, with no errors, but the response from API Gateway is 500 Internal Server Error.
I configured my api-gateway following this tutorial: Binary Support for API Integrations with Amazon API Gateway.
My lambda receives the base64Image, decode it and successfully upload to s3.
This is my lambda code:
def upload_image(event, context):
s3 = boto3.client('s3')
b64_image = event['base64Image']
image = base64.b64decode(b64_image)
try:
with io.BytesIO(image) as buffer_image:
buffer_image.seek(0)
s3.upload_fileobj(buffer_image, 'MY-BUCKET', 'image')
return {'status': True}
except ClientError as e:
return {'status': False, 'error': repr(e)}
This is what i'm receiving:
{
"message": "Internal server error"
}, with a 500 status code.
Obs: I'm not using lambda proxy integration.
You need to return a header in the response, e.g. in Python:
return {
"statusCode": 200,
'headers': { 'Content-Type': 'application/json' },
"body": json.dumps(body)
}
That example looks like it falls short on mapping the responses section in favor of a pass through. In which case changing your return to: return {'status': True, 'statusCode': 200} might work.
Generally speaking there are two paths when building a response with ApiGateway-Lambda. One is the lambda-proxy (where your lambda function defines the response), the other is where ApiGateway transforms your responses and generates the appropriate headers/status based on a mapping.
The path from the example is for the latter.
Personally I would change:
return {'status': True}
to return {'status': "Success"} And create a regex that looks for the word "Success" and "Error" respectively.
I have used this blog post successfully with this technique (it also describes at length the differences between the two approaches). Once you get one mapping working you could adjust it as is more appropriate for your implementation.
EDIT: hot tip these decorators are awesome and make python & lambda even cleaner/easier but mostly for the proxy setup
I have a lambda function invoked from my browser. I know that is working because the response is correct. In my lambda, I want to write into a dynamo table so I updated my function to include this logic.
When I test my function in the lambda console it works as expected. When the lambda is called from the browser (via API Gateway), it does not execute any of the new code that I added.
Here is my code:
#set-up table connection
dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
table = dynamodb.Table('XXXX')
tString = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
#print("Received event: " +
# json.dumps(event, indent=2))
#recieve parameters
if 'userid' in event:
userid = event['userid']
else:
userid = 'nothing'
if 'token' in event:
token = event['token']
else:
token = 'nothing'
if 'appid' in event:
appid = event['appid']
else:
appid = 'connection'
response = table.put_item(
Item = {
'ID': userid,
'token': 'test2',
'appid': 'test2',
'authApp': 'test2',
'authUser': 'test2'
})
return userid
Are you invoking your Lambda via API Gateway? If so, check the Lambda integration for the endpoint you are hitting in API Gateway and see if the version/alias of the Lambda function is hardcoded. You can find this by looking at the value of "Lambda Function" in the Integration Request section of the API Gateway method:
Lambda Function: my_function:dev
In the example above, this means your API Gateway is invoking the "dev" version of the "my_function" lambda.
Then check in the Lambda console if the version/alias you are invoking in the console, the one with the recent DynamoDB changes, matches the version/alias that is being invoked by the API Gateway.
I have spent a day or two smashing my head against the keyboard trying to figure out why my updates weren't being executed to realize that the API Gateway was pointing at a different/older version of my function.
I'll first explain the architecture of my system and then move to the question:
I have a REST API which is used as my API gateway. This server is build using Flask. I also have RabbitMQ cluster, and a client I wrote that listens to a specific queue and executes the tasks its getting.
Until now, all of my requests were asynchronous, so once a request has reached to the API gateway, a callback_uri field with URL to POST the results to provided as part of the request, and the API gateway was just responsible for sending the task to RabbitMQ and the worker processed the task, and at the end POSTed the results back to the callback URL.
My question is:
I want a new endpoint to be synchronous in the sense of, that the processing will be done still by the same worker as before, but I want to get the results back to the API gateway to return to the user and release the connection.
My current solution:
I'm sending a unique callback_uri as part of the request to the worker as before, but now the specific endpoint is implemented by my API gateway and allow both POST and GET methods, so the worker can POST the results once it finished, and my API gateway keeps polling the callback URL until a result is available and then return the result to the client.
Is there any other preferred option other than having a busy-waiting HTTP worker polling its own endpoint to get the results? but still be synchronous so the connection released only when the results become available?
Code for illustration only:
#app.route('/long_task', methods=['POST'])
#sync_request
def long_task():
try:
if request.get_json() is None:
return ERROR_MSG_NO_JSON, 400
create_and_send_request_to_rabbitmq()
return '', 200
except Exception as ex:
return ERROR_MSG_NO_DATA, 400
def sync_request(func):
def call(*args, **kwargs):
create_callback_uri()
result = func(*args, **kwargs)
status_code = result[1]
if status_code == 200:
result = get_callback_result()
return result
return call
def get_callback_result():
callback_uri = request.get_json()['callback_uri']
has_answer = False
headers = {'content-type': 'application/json'}
empty_response = {}
content = json.dumps(empty_response)
try:
with Timeout(seconds=SYNC_REQUEST_TIMEOUT_SECONDS):
while not has_answer:
response = requests.get(callback_uri, headers=headers)
if response.status_code == 200:
has_answer = True
content = response.content
else:
time.sleep(0.2)
except TimeoutException:
log.debug('Timed out on sync request for request %s ' % request)
return content, 200
So if I understand you correctly you want your backend to wait for the response from some worker (via RabbitMQ). You can achieve that by implementing rpc over rabbitmq. The key idea is to use the correlation id.
But definitely the most efficient way would be to run the client over websockets (or raw tcp socket if it is not a browser) and notify him directly when the job is done. That way you don't lock resources (client connection, rabbitmq queues) and you avoid performance hit (rpc).
In an AWS Lambda code, how can I get the HTTP method (e.g. GET, POST...) of an HTTP request coming from the AWS Gateway API?
I understand from the documentation that context.httpMethod is the solution for that.
However, I cannot manage to make it work.
For instance, when I try to add the following 3 lines:
if (context.httpMethod) {
console.log('HTTP method:', context.httpMethod)
}
into the AWS sample code of the "microservice-http-endpoint" blueprint as follows:
exports.handler = function(event, context) {
if (context.httpMethod) {
console.log('HTTP method:', context.httpMethod)
}
console.log('Received event:', JSON.stringify(event, null, 2));
// For clarity, I have removed the remaining part of the sample
// provided by AWS, which works well, for instance when triggered
// with Postman through the API Gateway as an intermediary.
};
I never have anything in the log because httpMethod is always empty.
The context.httpMethod approach works only in templates. So, if you want to have access to the HTTP method in your Lambda function, you need to find the method in the API Gateway (e.g. GET), go to the Integration Request section, click on Mapping Templates, and add a new mapping template for application/json. Then, select the application/json and select Mapping Template and in the edit box enter something like:
{
"http_method": "$context.httpMethod"
}
Then, when your Lambda function is called, you should see a new attribute in the event passed in called http_method which contains the HTTP method used to invoke the function.
API Gateway now has a built-in mapping template that passes along stuff like http method, route, and a lot more. I can't embed because I don't have enough points, but you get the idea.
Here is a screenshot of how you add it in the API Gateway console:
To get there navigate to AWS Console > API Gateway > (select a resource, IE - GET /home) > Integration Request > Mapping Templates > Then click on application/json and select Method Request Passthrough from dropdown shown in the screenshot above
I had this problem when I created a template microservice-http-endpoint-python project from functions.
Since it creates an HTTP API Gateway, and only REST APIs have Mapping template I was not able to put this work. Only changing the code of Lambda.
Basically, the code does the same, but I am not using the event['httpMethod']
Please check this:
import boto3
import json
print('Loading function')
dynamo = boto3.client('dynamodb')
def respond(err, res=None):
return {
'statusCode': '400' if err else '200',
'body': err.message if err else json.dumps(res),
'headers': {
'Content-Type': 'application/json',
},
}
def lambda_handler(event, context):
'''Demonstrates a simple HTTP endpoint using API Gateway. You have full
access to the request and response payload, including headers and
status code.
To scan a DynamoDB table, make a GET request with the TableName as a
query string parameter. To put, update, or delete an item, make a POST,
PUT, or DELETE request respectively, passing in the payload to the
DynamoDB API as a JSON body.
'''
print("Received event: " + json.dumps(event, indent=2))
operations = {
'DELETE': lambda dynamo, x: dynamo.delete_item(**x),
'GET': lambda dynamo, x: dynamo.scan(**x),
'POST': lambda dynamo, x: dynamo.put_item(**x),
'PUT': lambda dynamo, x: dynamo.update_item(**x),
}
operation = event['requestContext']['http']['method']
if operation in operations:
payload = event['queryStringParameters'] if operation == 'GET' else json.loads(event['body'])
return respond(None, operations[operation](dynamo, payload))
else:
return respond(ValueError('Unsupported method "{}"'.format(operation)))
I changed the code from:
operation = event['httpMethod']
to
operation = event['requestContext']['http']['method']
How do I get this solution?
I simply returned the entire event, checked the JSON and put it to work with the correct format.
If event appears an empty object, make sure you enabled proxy integration for the method. Proxy integration for an HTTP method adds request information into event.
See Use Lambda Proxy integration on API Gateway page.
If you are using API gateway, http method will be automatically passed to the event parameter when the lambda is triggered.
export const handler: Handler<APIGatewayProxyEvent> = async (
event: APIGatewayEvent,
context: Context
): Promise<APIGatewayProxyResult> => {
const httpMethod = event.httpMethod;
...
}