I have been migrating some backend-stuff from this stack:
Ubuntu -> Python -> Flask -> UWSGI -> NGINX
to this stack:
AWS Lambda (python) -> AWS API Gateway
I want the api calls to be direct from the browser to make it as snappy as possible. This migration has been a bit frustrating, so I wanted to capture some of the pitfalls for the next guy or gal. The two items are around encoding/decoding and CORS
BACKEND: FLASK
Here is how my flask app is set up to enable CORS and jsonification of responses
from flask import Flask, jsonify, request
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
@app.route("/")
def home():
resp = {'status':'hello world'}
return jsonify(resp)
if __name__ == "__main__":
app.run(port=5000, debug=True)
BACKEND: AWS Lambda
The CORS headers have to be inserted manually into the response from AWS. Also the routing is a bit different: you have to extract the path from the event so your lambda knows which method to use and set up a separate invoke_url for each, all tied back to the same lambda. By using one lambda for all the different paths on the endpoint, you enable container re-use with fewer cold starts:
def lambda_handler(event, context):
path = event['path']
resp = {
'statusCode': 200,
'headers': {
"Access-Control-Allow-Origin" : "*",
"Access-Control-Allow-Credentials" : True
},
'body': json.dumps({ 'event':'hello world' })
}
return resp
FRONTEND: Anvil
Within Anvil, you use the http.request library to make requests of the backend from the browser. Typically you would pass the json=True argument to decrypt the response and format as JSON, but for some reason I have not been able to get this to work properly with Lambda/API Gateway, and an Anvil Media Object (binary) is returned instead. The below method is intended to make an API call that can be used with Flask or with AWS Lambda:
from anvil import *
import json
def api_call(endpoint, req_data, method='POST', aws_lambda=True):
# make an API call at the specified endpoint with req_data
resp = http.request(
url = '{}/{}'.format(api_url, endpoint),
method=method,
data=req_data,
json=True)
if aws_lambda:
resp = json.loads(resp.get_bytes().decode())
return resp
One final note, the code above always uses a POST method. With API testing utilities like POSTMAN, you can include a JSON body as data in a GET request. With Anvil, GET requests cannot include this data so tend to default to using POST for everything.