Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
2
Help us understand the problem. What are the problem?

posted at

updated at

API Gateway ↔ Lambda ↔ Dynamo

はじめに

前回のものに API Gateway を繋いでみます。こんな感じがゴール

image.png

API Gateway

基本

GUI がかなり直感的でなので頭使わずに使える感じです

  1. エンドポイントのリソース /project 作って
  2. リソースにメソッド GET PUT POST DELETE 作って
  3. メソッド毎にLambda のコード project にマッピングするだけ

image.png

忘れがちなのが、APIの変更は「デプロイ」を押してデプロイしないといけないこと。これでとりあえず動くはず

image.png

APIキー

APIキーを使うときはちょっと面倒

  1. リソースの各メソッド毎にAPIキーでの認証を必須にする
  2. 「使用料プラン」を作成する(ここでAPIに対するレートリミットなどを設定)
  3. 「ステージ」を作成する(開発用とか商用とかそういう意味のステージのことで、ここでは dev_1 とした)
  4. 「APIキー」「使用料プラン」「ステージ」を関連づける

image.png

変更の反映に数分かかることもあるようですが、これでAPIキーを付けてコールできるはず

curl -s -X GET -H "x-api-key: your-api-key-here" https://<project-id>.execute-api.us-east-2.amazonaws.com/dev_1/projects | jq

Lambda 統合プロキシ

  • Lambda 統合プロキシ にチェックを入れる
  • メソッド(GET/PUT/POST/DELETE)をラムダ関数に紐づける1

することで、HTTPリクエストに含まれる様々なパラメターがそのまま Lambda に渡される

image.png

例えば、HTTPメソッドが POST なのか GET なのかが引き渡されるので Lambda 側でわかる.

こんな風に読んだ時に

curl -s -X GET -H "x-api-key:your-api-key-here" https://<api-id>.execute-api.us-east-2.amazonaws.com/dev_1/projects?say=ciao -d '{"say": "hello"}'

Lambda に event として渡されるパラメータはこんな感じ

event
{
  "resource": "/projects",
  "path": "/projects",
  "httpMethod": "GET",
  "headers": {
    "accept": "*/*",
    "content-type": "application/x-www-form-urlencoded",
    "Host": "<project-id>.execute-api.us-east-2.amazonaws.com",
    "User-Agent": "curl/7.64.1",
    "X-Amzn-Trace-Id": "Root=1-5fd44390-5f78ee5a289e7e3a0355bec2",
    "x-api-key": "your-api-key",
    "X-Forwarded-For": "***.**.**.**",
    "X-Forwarded-Port": "443",
    "X-Forwarded-Proto": "https"
  },
  "multiValueHeaders": {
    "accept": [
      "*/*"
    ],
    "content-type": [
      "application/x-www-form-urlencoded"
    ],
    "Host": [
      "<project-id>.execute-api.us-east-2.amazonaws.com"
    ],
    "User-Agent": [
      "curl/7.64.1"
    ],
    "X-Amzn-Trace-Id": [
      "Root=1-5fd44390-5f78ee5a289e7e3a0355bec2"
    ],
    "x-api-key": [
      "<your-api-key>"
    ],
    "X-Forwarded-For": [
      "***.**.**.**"
    ],
    "X-Forwarded-Port": [
      "443"
    ],
    "X-Forwarded-Proto": [
      "https"
    ]
  },
  "queryStringParameters": {
    "say": "ciao"
  },
  "multiValueQueryStringParameters": {
    "say": [
      "ciao"
    ]
  },
  "pathParameters": null,
  "stageVariables": null,
  "requestContext": {
    "resourceId": "<resource-id>",
    "resourcePath": "/projects",
    "httpMethod": "GET",
    "extendedRequestId": "Xa9-iG9kCYcFU8Q=",
    "requestTime": "12/Dec/2020:04:14:08 +0000",
    "path": "/dev_1/projects",
    "accountId": "<account-id>",
    "protocol": "HTTP/1.1",
    "stage": "dev_1",
    "domainPrefix": "<project-id>",
    "requestTimeEpoch": 1607746448145,
    "requestId": "06499aaf-9168-49d7-b4da-2a0a3ad5d4ad",
    "identity": {
      "cognitoIdentityPoolId": null,
      "cognitoIdentityId": null,
      "apiKey": "<your-api-key>",
      "principalOrgId": null,
      "cognitoAuthenticationType": null,
      "userArn": null,
      "apiKeyId": "<api-key-id>",
      "userAgent": "curl/7.64.1",
      "accountId": null,
      "caller": null,
      "sourceIp": "***.**.**.**",
      "accessKey": null,
      "cognitoAuthenticationProvider": null,
      "user": null
    }, 
    "domainName": "<project-id>.execute-api.us-east-2.amazonaws.com",
    "apiId": "<api-id>"
  },
  "body": "{\"say\": \"hello\"}",
  "isBase64Encoded": false
}

クライアントからのデータ bodyevent['body'] でアクセスできるので Lambda 側の CRUD するコードを前回から少し変えて、こんな感じにしときます


require 'json'
require 'aws-sdk-dynamodb'

def add_project(table, body)
  table.put_item({ item: body })  
end

def delete_project(table, body)
  params = { table_name: table, key: { 'project-id': body['project-id'] } }
  begin
    table.delete_item(params)
  rescue Aws::DynamoDB::Errors::ServiceError => error
    puts error.message
  end
end

def update_project(table, body)
  params = {
    table_name: table,
    key: { 'project-id': body['project-id'] },
    attribute_updates: {
      name: {
        value: body['name'],
        action: "PUT"
      }
    }
  }
  table.update_item(params)  
end

def list_project(table)
  scan_output = table.scan({ limit: 50, select: "ALL_ATTRIBUTES" })

  scan_output.items.each do |item|
    keys = item.keys
    keys.each do |k|
      puts "#{k}: #{item[k]}"
    end
  end
end

def lambda_handler(event:, context:)

  http_method = event['httpMethod'] # このへんを追加
  body = JSON.parse(event['body'])  # このへんを追加

  dynamodb = Aws::DynamoDB::Resource.new(region: 'us-east-2')
  table = dynamodb.table('project')

  case http_method
    when 'GET'    then list_project(table)
    when 'PUT'    then update_project(table, body)
    when 'POST'   then add_project(table, body)
    when 'DELETE' then delete_project(table, body)
    else 0
  end

  { statusCode: 200, body: JSON.generate(list_project(table)) }

end

例えばこんな感じでリクエスト投げると、HTTPのメソッドをみて CRUD 処理をする。ちなみに jq しとくと見やすいのでおすすめ

curl -s -X POST -d '{ "project-id":101, "name":"Cycling", "is-default":false }' -H "x-api-key: <your-api-key-here>" https://<project-id>.execute-api.us-east-2.amazonaws.com/dev_1/projects | jq

うむ、動いたぞ

シリーズ


  1. 今回のようなケースでは、実はメソッドとしてANYを使うことでメソッド個別にラムダ関数に紐づける必要はなくなる 

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
2
Help us understand the problem. What are the problem?