Amazon API Gateway and AWS Lambda Python version

Serverless by combining Amazon API Gateway and AWS Lambda I will write about realizing the architecture. In Python.

API Gateway

What is API Gateway?

API Gateway is a service that acts as a wrapper to provide Restful Web API. It seems that it is also combined with CloudFront, or implemented as a wrapper for it. CloudFront has the image of a CDN, but since AWS WAF is also combined with CloudFront, it is positioned as a Layer 7 network service, not just a CDN. It may have been. There are many competitions with only CDNs, and it seems that they will become commodities in a sense.

API Gateway components

sample

API Gateway Since it's a big deal, I created an API from Python. Please change the region, function and role at the beginning as appropriate.

createapi.py


# -*- coding: utf-8 -*-

import boto3
client = boto3.client('apigateway')
region = 'ap-northeast-1'
function = 'arn:aws:lambda:ap-northeast-1:AWS_ACCOUNT_ID:function:YOUR_LAMBDA_FUNCTION'
role = 'arn:aws:iam::AWS_ACCOUNT_ID:role/YOUR_IAM_ROLE_FOR_INVOCATION'


def create_api():
    rest_api = client.create_rest_api(
        name='sample01',
        description='sample api',
    )
    return rest_api['id']


def create_resource(rest_api_id):
    for resource in client.get_resources(
        restApiId=rest_api_id
    )['items']:
        if resource['path'] == '/':
            path_root_id = resource['id']
    new_resource = client.create_resource(
        restApiId=rest_api_id,
        parentId=path_root_id,
        pathPart='{hoge}',
    )
    return new_resource['id']


def setup_method(rest_api_id, resource_id):
    client.put_method(
        restApiId=rest_api_id,
        resourceId=resource_id,
        httpMethod='GET',
        authorizationType='NONE',
    )
    uri = 'arn:aws:apigateway:' + region + ':lambda:path/2015-03-31/functions/' + function + '/invocations'
    client.put_integration(
        restApiId=rest_api_id,
        resourceId=resource_id,
        httpMethod='GET',
        type='AWS',
        integrationHttpMethod='POST',
        uri=uri,
        credentials=role,
        requestTemplates={
            'application/json': get_request_template()
        },
    )
    client.put_integration_response(
        restApiId=rest_api_id,
        resourceId=resource_id,
        httpMethod='GET',
        statusCode='200',
        responseTemplates={
            'application/json': '',
        },
    )
    client.put_method_response(
        restApiId=rest_api_id,
        resourceId=resource_id,
        httpMethod='GET',
        statusCode='200',
        responseModels={
            'application/json': 'Empty',
        },
    )
    client.put_integration_response(
        restApiId=rest_api_id,
        resourceId=resource_id,
        httpMethod='GET',
        statusCode='400',
        selectionPattern='^\[400:.*',
        responseTemplates={
            'application/json': get_response_template(),
        },
    )
    client.put_method_response(
        restApiId=rest_api_id,
        resourceId=resource_id,
        httpMethod='GET',
        statusCode='400',
        responseModels={
            'application/json': 'Error',
        },
    )


def get_request_template():
    """
    ref. http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html
    """
    return """
{
  "pathParams": {
#foreach ($key in $input.params().path.keySet())
    "$key": "$util.escapeJavaScript($input.params().path.get($key))"#if ($foreach.hasNext),#end
#end
  }
}
"""


def get_response_template():
    """
    ref. http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html
    """
    return """
#set($data = $input.path('$'))
{
  "message" : "${data.errorMessage}"
}
"""


def deploy(rest_api_id):
    client.create_deployment(
        restApiId=rest_api_id,
        stageName='snapshot',
        stageDescription='snapshot stage',
    )
    client.update_stage(
        restApiId=rest_api_id,
        stageName='snapshot',
        patchOperations=[
            {
                'op': 'replace',
                'path': '/*/*/logging/loglevel',
                'value': 'INFO',
            },
        ],
    )

if __name__ == '__main__':
    rest_api_id = create_api()
    resource_id = create_resource(rest_api_id)
    setup_method(rest_api_id, resource_id)
    deploy(rest_api_id)
    api_url = 'https://' + rest_api_id + '.execute-api.' + region + '.amazonaws.com/snapshot/'
    print 'OK : {0}'.format(api_url + 'hoge')
    print 'NG : {0}'.format(api_url + 'fuga')

Lambda How to deploy Lambda is out of the question, so just the code.

lambdemo.py


# -*- coding: utf-8 -*-


def lambda_handler(event, context):
    hoge = event['pathParams']['hoge']
    if hoge == 'hoge':
        return {'message': 'hogehoge'}
    else:
        raise NotHogeError(hoge)


class NotHogeError(Exception):
    def __init__(self, hoge):
        self.hoge = hoge

    def __str__(self):
        return '[400:BadRequest] {0} is not hoge'.format(self.hoge)

Operation check

If you run createapi.py above, you will see two URLs like this (if there are no bugs).

createapi.Execution result of py


OK : https://xxxxx.execute-api.somewhere.amazonaws.com/snapshot/hoge
NG : https://xxxxx.execute-api.somewhere.amazonaws.com/snapshot/fuga

When you access the OK person

OK


$ curl https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/snapshot/hoge
{"message": "hogehoge"}

If you are NG, you will get angry.

NG


$ curl https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/snapshot/fuga  -w '%{http_code}\n'
{
  "message" : "[400:BadRequest] fuga is not hoge"
}
400

Description

Integration

Lambda URI

You need to specify the URI of the backend Lambda. It's complicated, but it seems to have a specific specification, which is determined by the region and the Lambda Function ARN. If you omit the last "invocations", it will return "500 internal server error". A sober fit point.

How to make a URI


uri = 'arn:aws:apigateway:' + region + ':lambda:path/2015-03-31/functions/' + function + '/invocations'

Request format conversion

Make a request (json) for Lambda using the Velocity template. See Reference (http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html) for available variables. Although it is hard coded in the sample, I think it is better to go out as a template file in actual operation.

request_template.vm


{
  "pathParams": {
#foreach ($key in $input.params().path.keySet())
    "$key": "$util.escapeJavaScript($input.params().path.get($key))"#if ($foreach.hasNext),#end
#end
  }
}

How to make Lambda

request

In the case of Python, the data converted in the above format is included in event as a dict.

Get request


hoge = event['pathParams']['hoge']

response

When returning a response from Lambda to API Gateway, use return or raise.

Successful completion


return {'message': 'hogehoge'}

Abnormal termination


raise NotHogeError(hoge)

As explained next, if you return it with return, it will be returned to the client with status code 200. (To be exact, it's the default status code ...)

Integration Response Convert the response from Lambda to a client response.

If you do not specify a template (set it to''), the response from lambda will be returned as it is. In this case, set the response model to Empty in Method Response.

It is easy to return the status code with 200, but it is also necessary to return it with a client error (400 series), a server error (500 series), or a redirect (300 series). For that purpose, it is necessary to associate with a regular expression what kind of response and which status code should be used.

Where are regular expressions looking

Now let's rewrite the sample Lambda.

When I try to return


else:
        #raise NotHogeError(hoge)
        return '[400:BadRequest] {0} is not hoge'.format(hoge)

Will return at 200


$ curl https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/snapshot/fuga -w '%{http_code}\n'
"[400:BadRequest] fuga is not hoge"200

The status code has become 200. .. .. Actually, you are not looking at the return value of the Lambda handler.

This is also


return {'message' : '[400:BadRequest] {0} is not hoge'.format(hoge)}

This is no good


return {'errorMessage' : '[400:BadRequest] {0} is not hoge'.format(hoge)}

This is what I arrived at in various ways.

exception.py


class NotHogeError(Exception):
    def __init__(self, hoge):
        self.hoge = hoge

    def __str__(self):
        """here!"""
        return '[400:BadRequest] {0} is not hoge'.format(self.hoge)

So, it seems that if you want to change the status code, you will throw an exception.

Response format conversion

In the case of normal system, Lambda response should be returned by pass-through, but if it is returned in XML or HTML instead of json, it needs to be converted. Also, in the case of an abnormal system, if it is pass-through, StackTrace will also be returned and it will be pitiful, so only the error message should be returned.

Since the Response Model called Error is originally defined, create a template according to it.

response_template.vm


#set($data = $input.path('$'))
{
  "message" : "${data.errorMessage}"
}

Summary

I thought that API Gateway was not so easy, but I felt that I should think about it if I design API properly in the first place.

Recommended Posts

Amazon API Gateway and AWS Lambda Python version
[Python] I wrote a REST API using AWS API Gateway and Lambda.
AWS CDK-Lambda + API Gateway (Python)
[AWS] Create API with API Gateway + Lambda
Try implementing a Cisco Spark bot with AWS Lambda + Amazon API Gateway (Python)
AWS Amplify + API Gateway + Lambda + Python returns a normal response
Create API with Python, lambda, API Gateway quickly using AWS SAM
LINE BOT (Messaging API) development with API Gateway and Lambda (Python) [Part 2]
[AWS SAM] Create API with DynamoDB + Lambda + API Gateway
[AWS] Try tracing API Gateway + Lambda with X-Ray
Make ordinary tweets fleet-like with AWS Lambda and Python
[Python] Scraping in AWS Lambda
Site monitoring and alert notification with AWS Lambda + Python + Slack
Anaconda and Python version correspondence table
Write AWS Lambda function in Python
Run Python on Schedule on AWS Lambda
Cooperation between python module and API
[Code] Module and Python version output
python string processing map and lambda
Notify HipChat with AWS Lambda (Python)
[AWS SAM] Introduction to Python version
Send images taken with ESP32-WROOM-32 to AWS (API Gateway → Lambda → S3)
I compared Node.js and Python in creating thumbnails using AWS Lambda
Install pip in Serverless Framework and AWS Lambda with Python environment
I tried ChatOps with Slack x API Gateway x Lambda (Python) x RDS
ImportError when trying to use gcloud package with AWS Lambda Python version
[AWS] Using ini files with Lambda [Python]
Python calling Google Cloud Vision API from LINE BOT via AWS Lambda
[AWS] Create a Python Lambda environment with CodeStar and do Hello World
Retrieving food data with Amazon API (Python)
Easy server monitoring with AWS Lambda (Python) and result notification in Slack
Understanding from the mechanism Twilio # 3-1 --AWS API Gateway + Lambda implementation Walkthrough (Part 1)
[AWS] Link Lambda and S3 with boto3
[Python] Run Headless Chrome on AWS Lambda
Connect to s3 with AWS Lambda Python
Easy REST API with API Gateway / Lambda / DynamoDB
Touch AWS with Serverless Framework and Python
Python + Selenium + Headless Chromium with aws lambda
A story about cross-compiling a python package for AWS Lambda and deploying it serverless
Periodically run a python program on AWS Lambda
[2020 version] How to install Python3 on AWS EC2
Differences between queryStringParameters and multiValueQueryStringParameters in AWS Lambda
View images on S3 with API Gateway + Lambda
Serverless application with AWS SAM! (APIGATEWAY + Lambda (Python))
[AWS / Lambda] How to load Python external library
Summary of studying Python to use AWS Lambda
Get Gmail subject and body with Python and Gmail API
Pass Cognito Id to Lambda via API Gateway
Try using ChatWork API and Qiita API in Python
Install ZIP version Python and pip on Windows 10
A note that connects to Lambda via AWS API Gateway (HTTP API) to process POST data
Try running a Schedule to start and stop an instance on AWS Lambda (Python)
PYTHON2.7 64bit version
"Linear regression" and "Probabilistic version of linear regression" in Python "Bayesian linear regression"
Develop, run, and deploy AWS Lambda remotely using lambda-uploader
I tried pipenv and asdf for Python version control
Terraform configured to launch AWS Lambda from Amazon SQS
Create Python version Lambda function (+ Lambda Layer) with Serverless Framework
Understanding with mathematical formulas and Python LiNGAM (ICA version)
Check types_map when using mimetypes on AWS Lambda (Python)
[AWS] Make friends with Lambda's JSON input (Python version)