配列をクエリー文字列としてAWS Lambdaに渡す


API Gateway、AWS Lambdaについて説明します.

概要


会社の開発過程で、axiosを使用してarrayをquery stringに入れてバックエンドを要求する際に正しく伝達できないという問題に遭遇しました.
同社のバックエンドはAWS Lambdaのサーバリースに基づくMSAアーキテクチャとして設計されており、理由が判明すると、APIゲートウェイがHTTPリクエストを処理する際に発生する問題であることが分かった.
そこで、本明細書では、AWS LambdaがAPIゲートウェイなどの外部サービスにどのように関連付けられているか、arrayがクエリ文字列に含まれている場合にAPIゲートウェイがどのようにグループ化されているかについて説明します.

AWS Lambdaと外部サービスの接続


AWS Lambda(以下、Ramdaと略す)は、AWS内の他のサービスに関連付けられて呼び出される関数です.
このとき、ramda関数は、ramda関数を実行するAWSサービスに依存するeventというパラメータを受信する.
APIゲートウェイでは、HTTP要求の情報(パス、メソッド、ボディなど)をjson形式でグループ化し、Ramda関数のイベントパラメータに渡す.したがって、ramda関数でリクエストの応答方法を決定できます.

特定のリクエストのイベント。json


しかしこのようにAPI GatewayとRamdaを接続して使用する際に不便な点は私が今送っているリクエストのイベントですjsonがどんな様子なのか分かりにくい.
AWSに開示されているまたはガイド人を除き、要求情報が記載されていれば、要求されたイベントに該当する.jsonを直接表示するサービスが見つかりません.
そこで、簡単なLambda関数を導入してテストすることにしました.
import { APIGatewayProxyHandler } from "aws-lambda";

export const handler: APIGatewayProxyHandler = async (event) => {
  return {
    statusCode: 200,
    body: JSON.stringify(event)
  }
};
リクエストが受信された場合、リクエストのイベント.jsonに応答するコード.
次に、サーバless frameworkを使用して配置します.serverless.ymlは次のように作成されます.
service: api-gateway-test

provider:
  name: aws
  runtime: nodejs12.x
  apiGateway:
    shouldStartNameWithService: true
  lambdaHashingVersion: 20201221

plugins:
  - serverless-plugin-typescript

functions:
  api:
    handler: src/index.handler
    events:
      - http:
          path: /{proxy+}
          method: any
pathは/{proxy+}に設定され、methodはanyに設定され、urlおよびすべてのメソッドの要求をramda関数に渡す.
次に、query stringを使用して配列を渡す方法を見て、APIゲートウェイが各query stringをどのようにグループ化するかを見てみましょう.

query stringを使用して配列を渡す方法。


query stringでarrayを渡す方法は次のとおりです.
1. ?a[]=1&a[]=2

2. ?a[0]=1&a[1]=2

3. ?a=1&a=2

4. ?a=[1,2]
(axiosのデフォルトで使用される方法は1です.)
次に、リクエストを上に配置したラムダ関数に送信し、APIゲートウェイが各query stringをラムダにどのようにグループ化するかを検証します.

1号メソッド結果



2号メソッド結果



3号メソッド結果



4号メソッド結果



実験で分かったことは以下の通りです.

  • APIゲートウェイはquery stringをグループ化し、queryStringParameters、multiValueQuery StringParametersフィールドを介して渡す.

  • 同じキーが複数ある場合、queryStringParametersには最後のkey-valueペアのみが含まれます.
  • 各queryStringParametersとmultiValueQuery StringParametersをqsのライブラリに分割します.
    簡単にJavascriptで書いた
    const qs = require("qs");
    
    // ?a[]=1&a[]=2
    const result1 = {
      queryStringParameters: qs.parse(
        qs.stringify({
          "a[]": "2",
        })
      ),
      multiValueQueryStringParameters: qs.parse(
        qs.stringify({
          "a[]": ["1", "2"],
        })
      )
    };
    console.log("1. ?a[]=1&a[]=2");
    console.log(result1);
    
    // ?a[0]=1&a[1]=2
    const result2 = {
      queryStringParameters: qs.parse(
        qs.stringify({
          "a[0]": "1",
          "a[1]": "2",
        })
      ),
      multiValueQueryStringParameters: qs.parse(
        qs.stringify({
          "a[0]": ["1"],
          "a[1]": ["2"],
        })
      )
    };
    console.log("2. ?a[0]=1&a[1]=2");
    console.log(result2);
    
    // ?a=1&a=2
    const result3 = {
      queryStringParameters: qs.parse(
        qs.stringify({
          "a": "2",
        })
      ),
      multiValueQueryStringParameters: qs.parse(
        qs.stringify({
          "a": ["1", "2"],
        })
      )
    };
    console.log("3. ?a=1&a=2");
    console.log(result3);
    
    // ?a=[1,2]
    const result4 = {
      queryStringParameters: qs.parse(
        qs.stringify({
          "a": "[1,2]",
        })
      ),
      multiValueQueryStringParameters: qs.parse(
        qs.stringify({
          "a": ["[1,2]"],
        })
      )
    };
    console.log("4. ?a=[1,2]");
    console.log(result4);
    出力結果は次のとおりです.
    1. ?a[]=1&a[]=2
    {
      queryStringParameters: { a: [ '2' ] },
      multiValueQueryStringParameters: { a: [ '1', '2' ] }
    }
    2. ?a[0]=1&a[1]=2
    {
      queryStringParameters: { a: [ '1', '2' ] },
      multiValueQueryStringParameters: { a: [ [Array], [Array] ] }
    }
    3. ?a=1&a=2
    {
      queryStringParameters: { a: '2' },
      multiValueQueryStringParameters: { a: [ '1', '2' ] }
    }
    4. ?a=[1,2]
    {
      queryStringParameters: { a: '[1,2]' },
      multiValueQueryStringParameters: { a: [ '[1,2]' ] }
    }
    出力結果から、問題を簡単に解決するには、チームでルールを作ったほうがいいです.

  • 1番、3番の方法の1つで、マルチバリュークォーリーStringParametersを使用します.

  • 2番のメソッドを使用して、queryStringParametersを使用します.

  • 4番の方法でqueryStringParametersでJSONみんなに解析してあげる
  • などルールがたくさんあります
    あるいはあなたも状況に応じてすべての症例を処理することができますか?

    トラブルシューティング


    上記のように、私はまずすべてのケースを処理しました.当社はAWS Lambda専用Webフレームワークを独自に作成し、requestパラメータ検証中
    function parse(value: any) {
      try {
        return JSON.parse(value);
      } catch (e) {
        return value;
      }
    }
    
    const value = schema.type === "array"
      ? (() => {
          const parsed = parse(queryStringParameters[key]);
          if (casted instanceof Array) {
            return parsed;
          }
          return multiValueQueryStringParameters[key];
        })()
      : queryStringParameters[key];
    (実際のコードとは全く同じではありません)もう処理しました.
    JSON.parseを使用して検証する理由は、qsの出力結果が表示された場合に推測されるためです./
  • 1番方法
  • JSON.解析に失敗しました(アレイ済み)
  • マルチValue Query StringParametersから
  • を読み込む
  • すべての値が
  • に正しく渡されました.
  • 2番方法
  • JSON.解析成功
  • Array条件
  • を通過
  • JSON.解析結果値
  • を返します.
  • すべての値が
  • に正しく渡されました.
  • 3番方法
  • JSON.解析成功
  • アレイインスタンス条件失敗
  • マルチValue Query StringParametersから
  • を読み込む
  • すべての値が
  • に正しく渡されました.
  • 4番方法
  • JSON.解析成功
  • Array条件
  • を通過
  • JSON.解析結果値
  • を返します.
  • すべての値が
  • に正しく渡されました.
    JSON.parseを使用すると、クエリー文字列を使用して配列を渡すすべてのインスタンスを正常に処理できます.
    フレームワークを変更してすべてのケースを処理する以外に、私たちのチームはquery stringをarrayとして渡すときに4つ目の方法で強制的に実行することにしました.
    axiosリクエストが次のように作成された場合:
    await axios.request({
      url: "/asdf",
      method: "GET",
      params: {
        a: [1, 2, 3],
        b: 123
      },
    });
    最終的には、次のような要求が送信されます.
    GET /asdf?a[]=1&a[]=2&a[]=3&b=123
    ただし、axiosのparamsSerializerオプションを使用すると、query stringを任意に操作できます.axiosは、開発者が設定したparamsオプション値をparamsSerializerの最初のパラメータとして開発者に渡し、qsのようなライブラリに操作します.
    await axios.request({
      url: "/asdf",
      method: "GET",
      params: {
        a: [1, 2, 3],
        b: 123
      },
      paramsSerializer: (params) => {
        const mappedParams = _.mapValues(params, (value) => {
          if (typeof value === "string") {
            return value;
          } else {
            return JSON.stringify(value);
          }
        });
        return qs.stringify(mappedParams);
      }
    });
    次に示すようにquery stringが変換されて送信されます.
    GET /asdf?a=[1,2,3]&b=123