TOP ▲ itcore TOPTIPSgo_api.php  タグ:go lambda dynamodb api gateway ソース

GO+Lambda+DynamoDB+API Gateway サンプルソース | itcore 2021年

main.go

package main

//-------------------------------------
// get-api/main.go DBのレコードを返す。
//-------------------------------------
import (
  "bytes"
  "context"
  "encoding/json"
  "errors"
  "github.com/aws/aws-lambda-go/events"
  "github.com/aws/aws-lambda-go/lambda"
  "github.com/aws/aws-lambda-go/lambdacontext"
  "github.com/aws/aws-sdk-go/aws"
  "github.com/aws/aws-sdk-go/aws/session"
  "github.com/aws/aws-sdk-go/service/dynamodb"
  "github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
  "log"
  "strconv"
  "strings"
  "time"
)

// 定数
const Version = "0.1"
const FuncName = "get" // 関数名 ログ用
const ApiKey = "xxx" // 外部で管理する
const HeaderApiKey = "X-Api-Key" // カスタムヘッダキー 先頭大文字のキャメル 文字とする。

// グローバル変数
var req RequestBody // リクエストデータ
var dbTable1 DbTable1   // DBデータ
var ErrorMessage = ""
var StatusCode = 200

// 長い型の別名
type (
  Request  = events.APIGatewayProxyRequest
  Response = events.APIGatewayProxyResponse
)

// リクエスト Body
type RequestBody struct {
  Id string `json:"id"` // ID
}

// バリデーション関数
func (req RequestBody) Validation() error {
  err := ""
  if "" == req.Id {
    err += "idを指定してください。\n"
  }
  if "" != err {
    return errors.New(err)
  }
}

// レスポンスデータ
type ResponseBody struct {
  Version      string `json:"version"`       // バージョン
  ErrorMessage string `json:"error_message"` // エラーメッセージ
  Id           string `json:"id"`            // ID
  Name         string `json:"name"`          // 名前
}

// レスポンスを作成する。
func MakeRes() (Response, error) {
  // レスポンスとして返すjson文字列を作る
  res := ResponseBody{
    Version:      Version,
    ErrorMessage: ErrorMessage,
    Id:           req.Id,
    Name:         dbTable1.Name,
  }
  jsonBytes, _ := json.Marshal(res)

  log.Println("[" + FuncName + "] end status=" + strconv.Itoa(StatusCode) + " " + ErrorMessage) // ログ出力
  return Response{
    Headers: map[string]string{
      "Content-type": "application/json",
      // CORS対応
      "Access-Control-Allow-Origin":  "*",
      "Access-Control-Allow-Methods": "*",
      "Access-Control-Max-Age":       "3600",
      // カスタムヘッダのCORS許可はうまくいっていない。
      "Access-Control-Allow-Headers":  "X-Api-Key",
      "Access-Control-Expose-Headers": "Access-Control-Request-Headers",
    },
    Body:       string(jsonBytes),
    StatusCode: StatusCode,
  }, nil
}

// DynamoDB テーブルデータ
type DbTable1 struct {
  Id   string `dynamodbav:"id"   json:id`   // ID
  Name string `dynamodbav:"name" json:name` // 名前
}

// APIハンドラ
func handler(ctx context.Context, request Request) (Response, error) {

  // ログを日本時間にする。
  time.Local = time.FixedZone("Asia/Tokyo", 9*60*60)
  log.Println("[" + FuncName + "] start") // ログ出力

  // リクエストデータ
  req.Id = request.QueryStringParameters["id"] // ID

  // ヘッダ APIキー認証
  if _, ok := lambdacontext.FromContext(ctx); !ok {
          ErrorMessage = "lambdacontext.FromContext ERROR122"
          StatusCode = 400
          return MakeRes()
  }
  // API Gateway経由だとカスタムヘッダのキーが小文字に変換される。
  // 以下の記事を参考にすると良いかも
 https://qiita.com/yuki_inoue/items/27555595b252923efe44
  api_key, _ := request.Headers[strings.ToLower(HeaderApiKey)]
  if "" == api_key {
    // ローカルだとカスタムヘッダのキーが先頭大文字のキャメル文字に変換される。
    api_key, _ = request.Headers[HeaderApiKey]
  }

  if ApiKey != api_key {
    // ブラウザのajaxからヘッダを送るとCORSエラーになるので一旦チェックを無 効にする。
    // bodyに持たせると良いかも
    log.Println("[" + FuncName + "]  APIキーエラー")
    // ErrorMessage = "正しい認証キーを指定してください。"
    // StatusCode = 400
    // return MakeRes()
  }

  // バリデーション
  if err := req.Validation(); err != nil {
    ErrorMessage = err.Error()
    StatusCode = 400
    return MakeRes()
  }

  //----------
  // DynamoDB
  //----------
  // DB接続
  sess, err := session.NewSession()
  if err != nil {
    ErrorMessage = "session.NewSession ERROR156 " + err.Error()
    StatusCode = 400
    return MakeRes()
  }
  db := dynamodb.New(sess)

  // 検索条件
  getParam := &dynamodb.GetItemInput{
    TableName: aws.String("table1"),
    Key: map[string]*dynamodb.AttributeValue{
      "id": {
        S: aws.String(req.Id),
      },
    },
    ConsistentRead: aws.Bool(true), // 整合性のある読み込み
  }

  // 検索実行
  result, err := db.GetItem(getParam)
  if err != nil {
    ErrorMessage = "db.GetItem ERROR176 " + err.Error()
    StatusCode = 400
    return MakeRes()
  }

  // 検索結果
  err = dynamodbattribute.UnmarshalMap(result.Item, &dbTable1)
  if err != nil {
    ErrorMessage = "dynamodbattribute.UnmarshalMap ERROR184 " + err.Error()
    StatusCode = 400
    return MakeRes()
  }
  // DB取得内容 ログ出力
  var buf bytes.Buffer
  json, _ := json.Marshal(dbTable1)
  buf.Write(json)
  log.Println("[" + FuncName + "] DynamoDB dbTable1 =" + buf.String())

  //-----------
  // 正常終了
  //-----------
  return MakeRes()
}

func main() {
  lambda.Start(handler)
}

samconfig.toml

version = 0.1
[default.deploy.parameters]
stack_name = "get-api"
s3_bucket = "itcorejp_dev"
s3_prefix = "get-api"
region = "ap-northeast-1"
confirm_changeset = false
capabilities = "CAPABILITY_IAM"

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  get-api

  SAM Template for get-api

Globals:
  Function:
    Timeout: 5

Resources:
  GetApiFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: get-api/
      Handler: get-api
      Runtime: go1.x
      Tracing: Active
      Events:
        CatchAll:
          Type: Api
          Properties:
            Path: /get_api
            Method: GET
      Environment:
        Variables:
          PARAM1: VALUE

Outputs:
  GetAPI:
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/get_api/"
  GetApiFunction:
    Value: !GetAtt GetApiFunction.Arn