前回: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で次のようなリソースを定義してテストしてみました。
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定義は以下のように変えます。
※サンプルなのでかなり適当に書きました。すみません。
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デプロイ編)