APIGWにおけるプログラミング
APIGWのアクションパイプラインには、forbidden_process
と呼ばれるプログラミングスポットが提供されています。APIの要求メッセージと応答メッセージそれぞれに対してスクリプトフックを設定できる機能です。
forbidden_process
の名前空間
logger
変数のダンプ確認などに使用するロガーです。ロガーの出力先は、ルーティングのデバッグトレース画面です。トレース画面には以下のようなフォーマットで出力されます。
[I 2020-11-24T10:49:41.934830+09:00:hotspot.routing./v1/examples.request_forbidden_process:7] URL変換: 'http://0.0.0.0/v1/examples' -> '0.0.0.0/v2/examples'
route
適用されたAPIルーティング情報(Routeオブジェクト)が格納されている変数です。
resources
リソースパス変数を格納している変数です。/v1/hoges/{hogeId}/hogehoges/{hogehogeId}
のようなリソースパスを持つAPIルーティングの場合に/v1/hoges/aaa/hogehoges/bbb
のようなアクセスがあった場合、resources
には以下の辞書データがセットされます。
{"hogeId": "aaa", "hogehogeId": "bbb"}
parameters
クエリパラメータを格納している変数です。/v1/hoges?name=aaa&name=bbb&status=Completed
のようなアクセスがあった場合、parameters
には以下の辞書データがセットされます。
{"name": ["aaa", "bbb"], "status": ["Completed"]}
session
HTTPセッションを格納している変数です。Qmonus SDKでは、tornado
をwebサーバとして利用しています。本オブジェクトは、tornado.web.RequestHandler
です。
主にデバッグ用途に参照を許可しているオブジェクトですのでプラグイン開発者が利用することを推奨していません。
finish
HTTP応答を送信してセッションを終了する関数です。code
キーワード引数でHTTP応答コードを指定します(デフォルトは200 Success
)。body
引数で応答ボディーを指定します。
body
に辞書やリスト型を指定した場合は、jsonに自動エンコードされます。headers
引数で応答ヘッダを指定することもできます。
await finish(body=dict(replay_back=True), code=200)
Note
HTTP応答は、finish
関数を利用する以外に以下のようにforbidden_process
内でreturn
することで応答を自動送信することができます。
return dict(reply_back=True)
とした場合、HTTP応答コード200でボディーを送信します。HTTPコードを指定したい場合は次のようにtupleでreturnしてください。return 202, dict(reply_back=True)
request
API要求情報が格納されている変数です。Qmonus SDKでは、tornado
をwebサーバとして利用しています。本オブジェクトは、tornado.httpclient.HTTPRequest
です。
よく使われるリクエストの上書き例を以下に記載しています。
- リクエストURLの書き換え
# v1をv2に変換する例(敢えてforbidden_processでやる必要はないレベルの変換)
origin = request.url
authoriry, endpoint = request.url.split("://")
tokens = endpoint.split("/")
if tokens[1]=="v1":
request.url = tokens[0]+"/v2/"+"/".join(tokens[2:])
logger.info("URL変換: %r -> %r" % (origin, request.url))
- リクエストボディーの書き換え
# GET以外のメソッドの場合、キーを補完する例
if request.method != GET:
content_type = request.headers.get("Content-Type", None)
if content_type.endswith("json"):
payload = json.loads(request.body)
if "new_key" not in payload:
payload.setdefault("new_key", True)
request.body = json.dumps(payload)
- マッシュアップ
# PUTメソッドの場合、要求された場合に子リソースの情報をGETして要求ボディーに補完する例
if request.method == PUT:
resp = await callout(path="/hoges/%s/hogehoges" % resources["id"])
if resp.error:
raise HTTPError(resp.code, reason=resp.error.__str__())
hogehoges = json.loads(resp.body)
payload = json.loads(request.body)
payload.setdefault("hogehoges", hogehoges)
request.body = json.dumps(payload)
- クエリのボディー統合
# クエリパラメータを要求ボディーにマージする例
payload = json.loads(request.body)
[payload.setdefault(k, v) for k, v in parameters.items()]
request.body = json.dumps(payload)
Tip
Scenarioにインストールされているプラグイン(ATOMやモジュール、ファンクションなど)をAPIGWのスクリプトで利用したい場合は、起動パラメータで--hotspot_sharing_mode=True
を設定してくだい。APIGWがScenarioのリソースを同期するため、共通コンテキスト(atom
、model
、context
)を利用することができるようになります。データベース検索だけを行うAPIなどステートレスなものは、シナリオを作成せずにルーティングのスクリプト処理部だけで記述できるため、通信オーバーヘッドを軽減することができます。
# employeeテーブルの情報をリストで返却する例
async with model.aiodb() as conn:
cursor = await conn.execute(model.employee.select())
employees = await cursor.fetchall()
return [rowtodict(i) for i in employees]
# employeeインスタンスを生成する例
if request.method == POST:
e = atom.employee(**json.loads(request.body))
await e.save()
return 201, e.localfields()
response
API応答情報が格納されている変数です。Qmonus SDKでは、tornado
をwebサーバとして利用しています。本オブジェクトは、tornado.httpclient.HTTPResponse
です。
よく使われるレスポンスの上書き例を以下に記載しています。
- レスポンスボディーの書き換え
# ターゲットサービスのAPI応答にUTCタイムスタンプを付与して返却する例
import datetime
if response.body:
payload = None
try:
payload = json.loads(response.body)
payload.setdefault("timestamp", datetime.datetime.now(tz=datetime.timezone.utc).isoformat())
except Exception as e:
raise HTTPError(500, reason="Unexpected backend response")
else:
# 注意: 応答ボディーをオーバーライドする場合は、response._bodyを置き換える必要があります。
response._body = payload