LoginSignup
7
4

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-02-24

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}
              }'

7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4