関数を動かす、ウェブのアドレス
お店が、/orders/sato というウェブのアドレスで、佐藤さんの注文を答えたいとします。そのためだけに、サーバーを動かしたくはありません。
API Gateway は、ウェブのリクエストを受けて、Lambda 関数に渡せます。関数は、ゲートウェイが期待する形で答えなければなりません。リクエストが間違っているときも、同じです。
このレッスンのゴール
メモ
API Gateway が関数に期待するレスポンスを、書くことができる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| リクエスト | 呼ぶ側が、ウェブのアドレスに送るもの | |
| レスポンス | 関数が返す答え | |
| ステータスコード | リクエストの結果を言う数 | 404 |
| ヘッダー | 本文の横に送る、名前つきの情報 | content-type |
| ボディ | リクエストやレスポンスの、本文の文字列 |
学生が入力するセルは、このプレビューでは空白です。
リクエストが入り、レスポンスが出る
ゲートウェイは、ウェブのリクエストをイベントにします。GET などのメソッドは requestContext.http.method にあり、パスは rawPath にあります。
関数は、statusCode、headers、body の3つを持つ辞書を返します。body は文字列でなければならないので、json.dumps を使います。これらは文書にある形で、あなたのスタックの出力ではありません。
実行: いちばん小さな玄関
import json
# HTTP API のリクエストの、文書にある形。削ってあります。
event = {
"requestContext": {"http": {"method": "GET"}},
"rawPath": "/orders/sato",
"headers": {"accept": "application/json"},
}
def handler(event, context):
method = event["requestContext"]["http"]["method"]
path = event["rawPath"]
data = {"you_asked": method + " " + path}
return {"statusCode": 200,
"headers": {"content-type": "application/json"},
"body": json.dumps(data)}
answer = handler(event, None)
print(answer["statusCode"])
print(answer["body"])
print(type(answer["body"]).__name__)
# event の "GET" を "POST" に変えて、もう一度実行しましょう。
ステータスコードは、誰が間違ったかを言う
2 で始まる 200 や 201 は、成功です。4 で始まる 400 や 404 は、呼ぶ側が間違ったものを求めた、という意味です。5 で始まるものは、関数のほうが失敗したときです。
間違ったリクエストは、故障ではありません。短いエラーを body に入れて、4 のコードを返します。関数がエラーで落ちると、呼ぶ側には、曖昧な 5 が届くだけです。直せることは何も伝わりません。
例: このレッスンで使うコード
| コード | 意味 | 使うとき |
|---|---|---|
| 200 | OK | データを見つけて返したとき |
| 201 | Created | 新しい注文を保存したとき |
| 400 | Bad request | 項目が足りない、間違っている、または body が JSON として読めないとき |
| 404 | Not found | そのパスや注文がないとき |
| 405 | Method not allowed | パスはあるが、そのメソッドはないとき |
| 415 | Unsupported media type | content-type が application/json でないとき |
学生が入力するセルは、このプレビューでは空白です。
よくある間違い
注意
悪い入力のとき、関数をそのまま落とす人が多いです。呼ぶ側には、曖昧なサーバーのエラーが届きます。先に入力を調べます。間違っているのが呼ぶ側なら、4 のコードを返し、何を直すかを言います。5 は、こちらの失敗のためにとっておきます。
実行: エラーをレスポンスにする
import json
def reply(status, data):
return {"statusCode": status,
"headers": {"content-type": "application/json"},
"body": json.dumps(data)}
def handler(event, context):
# キーがないのは呼ぶ側の間違いです。落とさずに 400 を返します。
if "queryStringParameters" not in event:
return reply(400, {"error": "add ?customer=name"})
name = event["queryStringParameters"]["customer"]
return reply(200, {"hello": name})
print(handler({"queryStringParameters": {"customer": "sato"}}, None)["statusCode"])
print(handler({}, None)["statusCode"])
print(handler({}, None)["body"])
# {"queryStringParameters": {}} で handler を呼ぶ3行目を足しましょう。何が起きますか。
リクエストを丁寧に読む
どのキーも、いつもあるとは限りません。クエリ文字列がないと、queryStringParameters はイベントに入りません。読む前に、in で調べます。
body は文字列で届きます。isBase64Encoded が true なら、文字列は符号化されているので、先に復号します。ヘッダーの名前は小文字で届くので、content-type を探します。
実行: base64 の body を読む
import json
import base64
event = {
"body": base64.b64encode(b'{"item": "tea"}').decode("utf-8"),
"isBase64Encoded": True,
}
text = event["body"]
print(text)
if event["isBase64Encoded"] is True:
text = base64.b64decode(text).decode("utf-8")
print(text)
print(json.loads(text)["item"])
# isBase64Encoded を False にして、もう一度実行しましょう。何がうまくいきませんか。
このモジュールで教えなかったこと
メモ
ここでの作業は、本物に似せた答えを使い、Python でオフラインで行いました。何もデプロイせず、本物のアカウントにも入らず、請求も見ていません。サービスを動かし続ける費用にも出会っていません。それらは次の段階で、アカウントの設定を確かめてくれる人と学ぶのが、いちばんです。