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のリソースを同期するため、共通コンテキスト(atommodelcontext)を利用することができるようになります。データベース検索だけを行う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