handlerの中身を変えずに eventの内容をデプロイ時とローカル時で同じにしたい。
##今回使う環境
- serverless (v1.26)
##まずは関数を作成
$ serverless create --template aws-nodejs --name my-service
##handlerを少し変更
'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
service: my-service
provider:
name: aws
runtime: nodejs6.10
+ plugins
+ - serverless-offline
functions:
hello:
handler: handler.hello
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);
};
ルーティングの設定
service: my-service
provider:
name: aws
runtime: nodejs6.10
plugins:
- serverless-offline
functions:
hello:
handler: handler.hello
+ events:
+ - http:
+ path: /hello
+ method: get
実行
$ serverless offline
Serverless: Starting Offline: dev/us-east-1.
Serverless: Routes for hello:
Serverless: GET /hello
Serverless: Offline listening on http://localhost:3000
別ターミナルで
$ curl "localhost:3000/hello?id=1&name=tarp"
{"message":"Go Serverless v1.0! Your function executed successfully!","input":{}}
元のターミナルを見てみると
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を変更
hello:
handler: handler.hello
events:
- http:
path: /hello
method: get
integration: lambda #デフォルトではlambda-proxy
##設定が終わったので実際にマッピングテンプレートを使用して試して見る
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}
}'
$ 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
$ 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\"}}"}⏎
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されたリクエストも同じようにマッピングする
|
#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
}
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}
}'