LoginSignup
16
14

More than 5 years have passed since last update.

Lambdaでお手軽サーバーレス入門(Lambdaのイベント from API Gateway編)

Last updated at Posted at 2018-09-29

前回:Lambdaでお手軽サーバーレス入門(API Gateway設定編)

こちらでGETとPOSTを定義しましたが、これに耐えうるようにLambdaもちょっと修正が必要です。
今回は、受け取れるイベントと、返すべきオブジェクトについて書いてみます。

Lambdaの1つ目の引数 event

初めて書いたLambda関数は以下のような内容でした。

import json

def lambda_handler(event, context):
    # TODO implement
    return {
        "statusCode": 200,
        "body": json.dumps(event)
    }

では、このeventには何が入ってくるのか、見てみます。
(API GatewayからLambda Proxy Integrationで呼び出される場合です。)

{
  "resource": "/hello-world",
  "path": "/hello-world",
  "httpMethod": "GET",
  "headers": {
    "X-header-bar": "sample-value2",
    "X-header-foo": "sample-value1"
  },
  "multiValueHeaders": {
    "X-header-bar": [
      "sample-value2"
    ],
    "X-header-foo": [
      "sample-value1"
    ]
  },
  "queryStringParameters": {
    "param1": "value1",
    "param2": "value2"
  },
  "multiValueQueryStringParameters": {
    "param1": [
      "value1"
    ],
    "param2": [
      "value2"
    ]
  },
  "pathParameters": null,
  "stageVariables": null,
  "requestContext": {
    "path": "/hello-world",
    "accountId": "199811994466",
    "resourceId": "40afgo",
    "stage": "test-invoke-stage",
    "requestId": "2c67ea73-c3fd-11e8-b124-73dfe8b888d1",
    "identity": {
      "cognitoIdentityPoolId": null,
      "cognitoIdentityId": null,
      "apiKey": "test-invoke-api-key",
      "cognitoAuthenticationType": null,
      "userArn": "arn:aws:iam::199811994466:user/ogawa@adasiaholdings.com",
      "apiKeyId": "test-invoke-api-key-id",
      "userAgent": "aws-internal/3 aws-sdk-java/1.11.398 Linux/4.9.119-0.1.ac.277.71.329.metal1.x86_64 OpenJDK_64-Bit_Server_VM/25.181-b13 java/1.8.0_181",
      "accountId": "199811994466",
      "caller": "AIDAJLEACL7U2KXWOMEDE",
      "sourceIp": "test-invoke-source-ip",
      "accessKey": "ASIAS5BNZCNRO4CEID4F",
      "cognitoAuthenticationProvider": null,
      "user": "AIDAJLEACL7U2KXWOMEDE"
    },
    "resourcePath": "/hello-world",
    "httpMethod": "GET",
    "extendedRequestId": "N_U-FHO9tjMFXhQ=",
    "apiId": "kmd0f95tsb"
  },
  "body": null,
  "isBase64Encoded": false
}

かいつまんで紹介

  • resource
    API Gatewayで定義したリソースのフルパスです。
    pathとはちょっと違います。(後述)
  • path
    API Gatewayで定義したリソースのフルパスです。
    resourceとはちょっと違います。(後述)
  • httpMethod
    GETやらPOSTやら。
  • headersおよびmultiValueHeaders
    クライアントから渡されたヘッダです。指定があれば、dictで取得できます。なければnullです。
  • queryStringParametersおよびmultiValueQueryStringParameters
    クライアントから渡されたクエリストリングです。指定があれば、dictで取得できます。なければnullです。
  • pathParameters
    resourceで変数を指定した場合に、変数名と実際の値のdictが取れます。なければnullです。
  • stageVariables
    ステージ変数。API Gatewayをデプロイすると取得できます。
    ※今回はまだDeployしていないのでnullです。
  • requestContext
    いろいろデータが取れます。 いつか認証周りを取り上げたときにはここを使います。
  • body
    POSTやPATCHのときの本文です。GETでは必然的にnullになります。
  • isBase64Encoded
    入力がバイナリの場合、bodyにはBase64エンコードされた状態で格納されます。そのときにtrueになります。

resourceとpathの違い(そしてpathParametersとの関連)

これを確かめるために、API Gatewayで次のようなリソースを定義してテストしてみました。

apigw13.png

Resource Pathには{hogehoge}のように、波括弧で囲った名前を指定しました。
この下にGETメソッドを定義して、呼び出してみました。

※一部抜粋しています。

{
  "resource": "/hello-world/{id}",
  "path": "/hello-world/123",
  "httpMethod": "GET",
  "pathParameters": {
    "id": "123"
  },
  "body": null,
  "isBase64Encoded": false
}

ここでresourceとpathに違いが出ました。
つまり、 resourceは定義しているものそのものpathは値を当てはめたものが渡されます。

そして、pathParametersにはidと、指定した値(ここでは123)がペアで入っています。
値は必ずstr(文字列)で入ります。

もしLambda関数内で、リソースによって処理を振り分けたい場合は、resourceとpathParametersを見て切り分けができます。

戻り値

以上のようなイベントを受け取って、Lambda関数では好きに処理ができますが、何かしら応答を返してあげる必要があります。
Handlerで指定された関数のreturnで返せばOKなのですが、いくつかルールがあります。

超最低限の例

def lambda_handler(event, context):
    return {
        'statusCode': 204
    }

最低限、statusCodeを返せばOKです。
もう少し読みやすくしたければ

from http import HTTPStatus

def lambda_handler(event, context):
    return {
        'statusCode': HTTPStatus.NO_CONTENT
    }

こうすると、あれ、204ってなんだっけ?がなくなりますね。

bodyも返したい

import json
from http import HTTPStatus

def lambda_handler(event, context):
    return {
        'statusCode': HTTPStatus.OK,
        'body': json.dumps({
            'message': 'Hello from Lambda & API Gateway'
        })
    }

Headerも返したい

import json
from http import HTTPStatus

def lambda_handler(event, context):
    return {
        'statusCode': HTTPStatus.OK,
        'headers': {
            'Content-Type': 'application/json'
        },
        'body': json.dumps({
            'message': 'Hello from Lambda & API Gateway'
        })
    }

バイナリを返したい

isBase64Encoded がミソです。

import base64
from http import HTTPStatus

def lambda_handler(event, context):
    body = b'何かしらバイナリ(PDFとか)'

    return {
        'statusCode': HTTPStatus.OK,
        'headers': {
            'Content-Type': 'application/pdf',
            'Content-Disposition': 'attachment; filename="sample.pdf"'
        },
        'body': base64.b64encode(body).decode('utf-8'),
        'isBase64Encoded': true
    }

サンプルソース

最初に作ったLambda関数と、API Gateway定義は以下のように変えます。
※サンプルなのでかなり適当に書きました。すみません。

lambda_function.py
import json
from http import HTTPStatus

USERS = {
    'railgun': {
        'name': 'Mikoto Misaka',
        'age': 14,
        'city': 'Gakuen-toshi',
        'country': 'Japan'
    },
    'imagine-breaker': {
        'name': 'Touma Kamijo',
        'age': 15,
        'city': 'Gakuen-toshi',
        'country': 'Japan'
    },
    'index': {
        'name': 'Index-Librorum-Prohibitorum',
        'age': 14,
        'city': 'London?',
        'country': 'United Kingdom'
    }
}

def lambda_handler(event: dict, context):
    http_method = event['httpMethod']
    resource = event['resource']

    response_status = None

    if http_method == 'GET':
        response_body = {
            'method': 'get information',
            'data': None
        }
        if resource == '/hello-world':
            response_body['data'] = USERS
            response_status = HTTPStatus.OK

        elif resource == '/hello-world/{id}':
            response_body['data'] = USERS.get(event['pathParameters']['id'])
            if not response_body['data']:
                response_status = HTTPStatus.NOT_FOUND
            else:
                response_status = HTTPStatus.OK

    elif http_method == 'POST':
        response_body = {
            'method': 'insert information',
            'data': json.loads(event['body']) if event['body'] else None
        }
        response_status = HTTPStatus.CREATED

        # Do nothing (it's sample)

    return {
        'statusCode': response_status,
        'body': json.dumps(response_body)
    }

レールガン、やっぱりかっこいいっすね。

次回予告

次回は、これで実際に外部から呼び出してみます。
次回:Lambdaでお手軽サーバーレス入門(API Gatewayデプロイ編)

16
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
14