Help us understand the problem. What is going on with this article?

Serverless Framework eventの内容をローカルとデプロイ時で入力を出来るだけ同じにする

More than 1 year has passed since last update.

handlerの中身を変えずに eventの内容をデプロイ時とローカル時で同じにしたい。

今回使う環境

  • serverless (v1.26)

まずは関数を作成

$ serverless create --template aws-nodejs --name my-service

handlerを少し変更

handler.js
'use strict';

module.exports.hello = (event, context, callback) => {
  const response = {
    statusCode: 200,
    body: JSON.stringify({
      message: 'Go Serverless v1.0! Your function executed successfully!',
      input: {
        id: event.id,
        name: event.name
      }
    })
  };

  callback(null, response);
};

テストデータの作成

$ echo \
  '{
     "id": 1,
     "name": "kuro"
  }' >> event.json

実行

$ serverless invoke local -f --path event.json

{
    "statusCode": 200,
    "body": "{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":{\"id\":1,\"name\":\"kuro\"}}"
}

そうですね、invoke localでテスト用のjsonを使用した時はevent.keyでそのままアクセスできますね?
ではデプロイした時のeventがどうなっているのかを見てみます。

デプロイ時のevent

ローカルで結果を見るために

ローカルで手軽に結果を見るためにserverlessのプラグインserverless-offlineをインストールします。

$ npm init
$ npm install -D serverless-offline
serverless.yml
service: my-service

provider:
  name: aws
  runtime: nodejs6.10

+ plugins
+   - serverless-offline 

functions:
  hello:
    handler: handler.hello

handler.jsを変更

handler.js
'use strict';

module.exports.hello = (event, context, callback) => {
+ console.log("[LOG]", event);
  const response = {
    statusCode: 200,
    body: JSON.stringify({
      message: 'Go Serverless v1.0! Your function executed successfully!',
      input: {
        id: event.id,
        name: event.name
      }
    })
  };

  callback(null, response);
};

ルーティングの設定

serverless.yml
service: my-service

provider:
  name: aws
  runtime: nodejs6.10

plugins:
  - serverless-offline

functions:
  hello:
    handler: handler.hello
+    events:
+      - http:
+          path: /hello
+          method: get

実行

window1
$ serverless offline

Serverless: Starting Offline: dev/us-east-1.

Serverless: Routes for hello:
Serverless: GET /hello

Serverless: Offline listening on http://localhost:3000

別ターミナルで

window2
$ curl "localhost:3000/hello?id=1&name=tarp"
{"message":"Go Serverless v1.0! Your function executed successfully!","input":{}}

元のターミナルを見てみると

window1
Serverless: GET /hello (λ: hello)
[LOG] { headers:
   { Host: 'localhost:3000',
     'User-Agent': 'curl/7.54.0',
     Accept: '*/*' },
  path: '/hello',
  pathParameters: null,
  requestContext:
   { accountId: 'offlineContext_accountId',
     resourceId: 'offlineContext_resourceId',
     apiId: 'offlineContext_apiId',
     stage: 'dev',
     requestId: 'offlineContext_requestId_',
     identity:
      { cognitoIdentityPoolId: 'offlineContext_cognitoIdentityPoolId',
        accountId: 'offlineContext_accountId',
        cognitoIdentityId: 'offlineContext_cognitoIdentityId',
        caller: 'offlineContext_caller',
        apiKey: 'offlineContext_apiKey',
        sourceIp: '127.0.0.1',
        cognitoAuthenticationType: 'offlineContext_cognitoAuthenticationType',
        cognitoAuthenticationProvider: 'offlineContext_cognitoAuthenticationProvider',
        userArn: 'offlineContext_userArn',
        userAgent: 'curl/7.54.0',
        user: 'offlineContext_user' },
     authorizer:
      { principalId: 'offlineContext_authorizer_principalId',
        claims: undefined },
     resourcePath: '/hello',
     httpMethod: 'GET' },
  resource: '/hello',
  httpMethod: 'GET',
  queryStringParameters: { id: '1', name: 'kuro' },
  stageVariables: null,
  body: null,
  isOffline: true }
Serverless: [200] {"statusCode":200,"body":"{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":{}}"}

このように実際には必要ないものがeventの中に入っており。 パラメータにアクセスするにはevent.queryStringParametersでアクセスしなくてはいけません。
HTTPmethodがpostの場合は JSON.parse(event.body)のようにしてあげなくてはいけません。
デプロイ環境かローカル環境かの場合分けとかなんかあんまりしたくありません。。
httpMethodもヘッダーの情報もリソースの情報も小さいapiを作るのに必要ありませんよね。

なので、これを解決するために serverless0.5系お馴染みのmapping-templateを使うことにします。

mapping-templateを有効にするために

serverless 1系でmapping-templateを使用するにはserverlessのカスタムリクエストテンプレートというものを使います。使うためには serverlessのlambdaとhttpエンドポイントの統合の設定を変えなければなりません。
function句を変えていきます。

serverless.ymlを変更

serverless.yml
  hello:
    handler: handler.hello
    events:
      - http:
          path: /hello
          method: get
          integration: lambda #デフォルトではlambda-proxy

設定が終わったので実際にマッピングテンプレートを使用して試して見る

serverless.ymlを変更

serverless.yml
service: my-service

provider:
  name: aws
  runtime: nodejs6.10

plugins:
  - serverless-offline

custom:
  # query string
  idFromParams: '"id": "$input.params("id")"'  
  nameFromParams: '"name": "$input.params("name")"'
  # json body
  idFromJson: '"id": $input.json("$.id")'
  nameFromJson: '"name": $input.json("$.name")'

functions:
  getHello:
    handler: handler.hello
    events:
      - http:
          path: /hello
          method: get
          integration: lambda
          request:
            template:
              application/json: '{
                ${self:custom.idFromParams},
                ${self:custom.nameFromParams}
              }'
  postHello:
    handler: handler.hello
    events:
      - http:
          path: /hello
          method: post
          integration: lambda
          request:
            template:
              application/json: '{
                ${self:custom.idFromJson},
                ${self:custom.nameFromJson}
              }'

window1
$ serverless offline
Serverless: Starting Offline: dev/us-east-1.

Serverless: Routes for getHello:
Serverless: GET /hello

Serverless: Routes for postHello:
Serverless: POST /hello

Serverless: Offline listening on http://localhost:3000
window2
$ curl "localhost:3000/hello?id=1&name=taro"
{"statusCode":200,"body":"{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":{\"id\":\"1\",\"name\":\"taro\"}}"}
window1
serverless offline
Serverless: Starting Offline: dev/us-east-1.

Serverless: Routes for getHello:
Serverless: GET /hello

Serverless: Routes for postHello:
Serverless: POST /hello

Serverless: Offline listening on http://localhost:3000

Serverless: GET /hello (λ: getHello)
Serverless: The first request might take a few extra seconds
[LOG] { id: '1', name: 'taro', isOffline: true, stageVariables: {} }
Serverless: [200] {"statusCode":200,"body":"{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":{\"id\":\"1\",\"name\":\"taro\"}}"}

postでjsonをうけとった時も同じようにeventの値にアクセスできるはずです。
このようにserverless-offlineによって設定される値はあるものの使わない値がeventの中に入らず、handlerの中身を変えずにeventの内容にデプロイ時もローカルと同じようにアクセスできるようになりました。

今回使用したファイルたち

おまけ

url encodeされたリクエストも同じようにマッピングする

config/serverless/urlDecodeTmpl.yml
|
    #if ($context.httpMethod == "POST")
      #set($rawAPIData = $input.path('$'))
      #set($rawAPIData = $rawAPIData.replace('"', '\"').replace('%40', '@'))
    #else
      #set($rawAPIData = "")
    #end

    #set($countAmpersands = $rawAPIData.length() - $rawAPIData.replace("&", "").length())
    #if ($countAmpersands == 0)
      #set($rawPostData = $rawAPIData + "&")
    #end

    #set($tokenisedAmpersand = $rawAPIData.split("&"))
    #set($tokenisedEquals = [])

    #foreach( $kvPair in $tokenisedAmpersand )
      #set($countEquals = $kvPair.length() - $kvPair.replace("=", "").length())
      #if ($countEquals == 1)
        #set($kvTokenised = $kvPair.split("="))
      #if ($kvTokenised[0].length() > 0)
        #set($devNull = $tokenisedEquals.add($kvPair))
      #end
      #end
    #end
    {
    #foreach( $kvPair in $tokenisedEquals )
      #set($kvTokenised = $kvPair.split("="))
      #if($kvTokenised.size() == 2 && $kvTokenised[1].length() > 0)
        #set($kvValue = $kvTokenised[1])
      #else
        #set($kvValue = "")
      #end
      #if( $foreach.hasNext )
        #set($itemDelimiter = ",")
      #else
        #set($itemDelimiter = "")
      #end
    "$kvTokenised[0]" : "$kvValue"$itemDelimiter
    #end
    }
serverless.yml
service: my-service

provider:
  name: aws
  runtime: nodejs6.10

plugins:
  - serverless-offline

custom:
+  urlDecode: ${file(./config/serverless/urlDecodeTmpl.yml)
  # query string
  idFromParams: '"id": "$input.params("id")"'
  nameFromParams: '"name": "$input.params("name")"'
  # json body
  idFromJson: '"id": $input.json("$.id")'
  nameFromJson: '"name": $input.json("$.name")'

functions:
  getHello:
    handler: handler.hello
    events:
      - http:
          path: /hello
          method: get
          integration: lambda
          request:
            template:
+              application/x-www-form-urlencoded: ${self:custom.urlDecode}
              application/json: '{
                ${self:custom.idFromParams},
                ${self:custom.nameFromParams}
              }'
  postHello:
    handler: handler.hello
    events:
      - http:
          path: /hello
          method: post
          integration: lambda
          request:
            template:
+              application/x-www-form-urlencoded: ${self:custom.urlDecode}
              application/json: '{
                ${self:custom.idFromJson},
                ${self:custom.nameFromJson}
              }'

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした