Go言語 + API Gateway + Lambdaの組み合わせでPOSTした値を自由に操作する方法
概要
API Gateway + AWS Lambda(golang)の組み合わせで、POSTをした際投げたJSONデータをLambda内でキャッチして自由にいじる設定方法と実装方法。
仕様
curlでPOSTした時、USER_ID, USER_NAME, AGE, SEND_PUSH, SEND_EMAILを受け取り、これをgolangの構造体に変換しログに出力する。
なお、serverless-frameworkの使用を前提とする。
結論
以下三つのファイルを用意し、Makefileのある階層で`$ make deploy`コマンドを打てば、標題の機能を備えたLambda + API Gatewayが生成される。
1. serverless.yml
2. main.go
3. Makefile
serverless.yml
service: go-lambda-post-control
frameworkVersion: '>=1.28.0 <2.0.0'
provider:
name: aws
runtime: go1.x
stage: dev
region: ap-northeast-1
profile: your-profile
package:
exclude:
- ./**
include:
- ./bin/**
functions:
go-lambda-post-control:
handler: bin/go-lambda-post-control
timeout: 300
events:
- http:
path: eventtest
method: post
Makefile
.PHONY: build clean deploy
build:
env GOOS=linux go build -ldflags="-s -w" -o bin/go-lambda-post-control main.go
clean:
rm -rf ./bin
deploy: clean build
sls deploy --verbose
main.go(AWS Lambdaのソース)
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
// ※POSTされてくるJSONデータの内容を構造体で定義する
type Request struct {
UserID int `json:"user_id,string"`
UserName string `json:"user_name"`
Age int `json:"age,string"`
SendPush bool `json:"send_push,string"`
SendEmail bool `json:"send_email,string"`
}
// JSONの形のテキストデータをgolangの構造体に変換するための関数
func ConvertInputDataToStruct(inputs string) (*Request, error) {
var req Request
err := json.Unmarshal([]byte(inputs), &req)
if err != nil {
return nil, err
}
return &req, nil
}
// ↓ APIGatewayProxyRequest型で引数にPOSTした内容を受け取り、APIGatewayProxyResponseで変換するのが作法
func Handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
// テキストデータをgolang構造体に変換
req, err := ConvertInputDataToStruct(request.Body)
if err != nil {
return events.APIGatewayProxyResponse{
Body: err.Error(),
StatusCode: 500,
}, err
}
// 変換後の構造体をログに出力する
fmt.Println("This is Request struct in aws lambda")
fmt.Printf("(%%#v) %#v\n", req)
return events.APIGatewayProxyResponse{
Body: "Success to convert post data to golang struct.",
StatusCode: 200,
}, nil
}
func main() {
lambda.Start(Handler)
}
Githubのリポジトリはこちら↓。
ワークショップ
ここからは説明編。
実際に作りながら、結論で開示したソースの内容を説明していく。
Step1. セットアップ
まずは以下のコマンドを打ったあと、ファイルを移動する。
# 初期スクリプト作成
$ sls create --template aws-go --path go-lambda-post-control --name go-lambda-post-control
# 不要なファイルを削除し、このような形になるようにファイルを移動しつつ、ここにないファイルは不要なので削除する。
go-lambda-post-control/
├── Makefile
├── bin
│ └── hello
├── main.go
└── serverless.yml
各ファイルを以下のようにデフォルトの記述に少し追記する。
serverless.yml
service: go-lambda-post-control
frameworkVersion: '>=1.28.0 <2.0.0'
provider:
name: aws
runtime: go1.x
stage: dev
region: ap-northeast-1
profile: your-profile # ~/.aws/credentialsに定めている差し先を指定するために追記。defaultの差し先にDeployしたい場合は不要。
package:
exclude:
- ./**
include:
- ./bin/**
functions:
go-lambda-post-control: # lambda関数名に変更する。デフォルトだとhello。
handler: bin/go-lambda-post-control # ここもmakeファイルのdeployした名前に変更。
timeout: 300
events:
- http:
path: eventtest
method: post
Makefile
.PHONY: build clean deploy
build:
env GOOS=linux go build -ldflags="-s -w" -o bin/go-lambda-post-control main.go
clean:
rm -rf ./bin
deploy: clean build
sls deploy --verbose
ここまで終わったら、Deployする。
$ make deploy
# 以下のような表示が出てくるので、ここのPOST の部分をコピーしてcurlのラストにペースト
・
・
・
Service Information
service: go-lambda-post-control
stage: dev
region: ap-northeast-1
stack: go-lambda-post-control-dev
resources: 11
api keys:
None
endpoints:
POST - https://your-endpoint # <= ココをコピーする!!!
functions:
go-lambda-post-control: go-lambda-post-control-dev-go-lambda-post-control
layers:
None
・
・
# POSTをしてみると、serverless frameworkがデフォで打ち返す文字列が表示される。
$ curl -X POST -H "Content-Type: application/json" -d '{"user_id": "12345", "user_name":"テストユーザー", "age":"25", "send_push":"true", "send_email": "false"}' https://your-endpoint
{"message":"Go Serverless v1.0! Your function executed successfully!"}
Step2. Lambdaのソース追記
以下のようにLambdaの中身を書き換える。
処理の順番
1. POSTされてきたデータの変換先となる構造体を定義
2. events.APIGatewayProxyRequest型でPOSTされたデータをキャッチ
3. 上で受け取ったデータの中を、構造体に変換
4. ログ出力
※ `Unmarchal`はJSONの形式になっているテキストを構造体へ変換してくれる優れもの。しかし変換先の構造体の要素にintやboolなどのstring以外のものが入っていた場合、エラーで落ちてしまう。ゆえに`json:"user_id,string"`といった書き方をしている。公式はこのように発表している↓。
main.go
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
// ※POSTされてくるJSONデータの内容を構造体で定義する
type Request struct {
UserID int `json:"user_id,string"`
UserName string `json:"user_name"`
Age int `json:"age,string"`
SendPush bool `json:"send_push,string"`
SendEmail bool `json:"send_email,string"`
}
// JSONの形のテキストデータをgolangの構造体に変換するための関数
func ConvertInputDataToStruct(inputs string) (*Request, error) {
var req Request
err := json.Unmarshal([]byte(inputs), &req)
if err != nil {
return nil, err
}
return &req, nil
}
// ↓ APIGatewayProxyRequest型で引数にPOSTした内容を受け取り、APIGatewayProxyResponseで変換するのが作法
func Handler(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
// テキストデータをgolang構造体に変換
req, err := ConvertInputDataToStruct(request.Body)
if err != nil {
return events.APIGatewayProxyResponse{
Body: err.Error(),
StatusCode: 500,
}, err
}
// 変換後の構造体をログに出力する
fmt.Println("This is Request struct in aws lambda")
fmt.Printf("(%%#v) %#v\n", req)
return events.APIGatewayProxyResponse{
Body: "Success to convert post data to golang struct.",
StatusCode: 200,
}, nil
}
func main() {
lambda.Start(Handler)
}
これでcurlをすれば、`Success to convert post data to golang struct.`の文字が返ってくるはず。
また、CloudWatchLogsを見れば、POSTした内容が構造体として出力されている。
余談:!死ぬほどハマりポイント! lambda-integration
丸2日ほど溶かして悔しかったので共有。
きちんとserverless.ymlに明示してdeployしようとしてserverless.ymlにlambda: integrationをつけるとAPIGatewayProxyRequest.body への変換がこける。こちらを外してもserverless frameworkの方ではAPI GatewayとLambdaをくっつけてくれるので、外して実装した。
serverless.ymlのラストの部分を抜粋
functions:
go-lambda-post-control:
handler: bin/go-lambda-post-control
timeout: 300
events:
- http:
path: eventtest
method: post
integration: lambda # <= ここ!書いちゃダメ!!
なお、これを書いちゃった場合、`lambda.Start(Handler)`までは処理が到達するが、`func Handler`の中にまでは処理が到達せず、以下のエラーを吐いて落ちる。
json: cannot unmarshal object into Go struct field APIGatewayProxyRequest.body of type string: UnmarshalTypeError null
英語のIssueのログに同様に悩んでいる海外の人がいた。
おしまい。