Kintone REST APIを呼ぶAmazon APIGatewayをCloudFormationで作る

これまで、以下の記事でAPIGatewayからKintone　REST APIを呼んでみましたが、CloudFormationがAPIGatewayに対応したので、ここまでやったことをCloudFormationのtemplate化してみました。（swaggerは使ったこと無い :sweat_smile: ）

__注意__
__kintone REST APIは同時アクセス数がドメインごとに10と制限されています。__

> ドメインへの同時アクセス
> 　・APIによる同時アクセス数はドメインごとに10が上限です。
>
> [REST APIの共通仕様](https://cybozudev.zendesk.com/hc/ja/articles/201941754-REST-API%E3%81%AE%E5%85%B1%E9%80%9A%E4%BB%95%E6%A7%98#step12)

実システムで利用する場合は、API Gatewayのスロットリング設定やエラーのリトライなどを考慮する必要が有ります。

注意ここまで


- [APIGatewayのHTTP ProxyでKintoneのREST APIを呼ぶ（１）](http://qiita.com/monamu/items/a257a57e3236fca91ddc)
- [APIGatewayのHTTP ProxyでKintoneのREST APIを呼ぶ（２）](http://qiita.com/monamu/items/900ad90a47a87d98ec45)

上記の記事でやったことの他にCORS対応もやってしまいます。

CORSについては、[greenspaさんがわかりやすくまとめてくれています。](http://qiita.com/greenspa/items/b945d93b8610dda30640)

# ソース

```json
{
  "AWSTemplateFormatVersion":"2010-09-09",
  "Description":"Sample stack for Kintone Proxy",
  "Parameters":{
    "StageName":{
      "Type":"String",
      "MinLength":1,
      "Default":"prod"
    },
    "KintoneSubdomain":{
      "Type":"String",
      "MinLength":1
    },
    "KintoneApiToken":{
      "Type":"String",
      "MinLength":1
    },
    "KintoneAppId":{
      "Type":"String",
      "MinLength":1
    },
    "AccessControlAllowOrigin":{
      "Type":"String",
      "MinLength":1,
      "Default":"*"
    }
  },
  "Metadata":{
    "AWS::CloudFormation::Interface":{
      "ParameterGroups":[
        {
          "Label":{"default":"API Gateway configuration."},
          "Parameters":["StageName"]
        },
        {
          "Label":{"default":"Kintone configuration."},
          "Parameters":["KintoneSubdomain","KintoneApiToken","KintoneAppId"]
        }
      ]
    }
  },
  "Resources":{
    "RestApi":{
      "Type" : "AWS::ApiGateway::RestApi",
      "Properties" : {
        "Description" : {"Fn::Join":["",["Create from ",{"Ref":"AWS::StackName"}]]},
        "FailOnWarnings" : true,
        "Name" : {"Fn::Join":["",[{"Ref":"AWS::StackName"},"-Api"]]}
      }
    },
    "ApiMethodPOST":{
      "Type" : "AWS::ApiGateway::Method",
      "Properties" : {
        "ResourceId" : {"Fn::GetAtt":["RestApi","RootResourceId"]},
        "RestApiId":{"Ref":"RestApi"},
        "HttpMethod" : "POST",
        "AuthorizationType" : "NONE",
        "ApiKeyRequired" : false,
        "Integration" : {
          "Type" : "HTTP",
          "Uri" : "https://${stageVariables.subdomain}.cybozu.com/k/v1/record.json",
          "IntegrationHttpMethod" : "POST",
          "RequestParameters" : {
            "integration.request.header.X-Cybozu-API-Token":"stageVariables.apiToken"
          },
          "RequestTemplates" : {
            "application/json":{"Fn::Join":["",[
                "{","\n",
                "    \"app\":\"${stageVariables.appId}\",","\n",
                "    \"record\":{","\n",
                "        \"item1\":{\"value\":$input.json('$.item1')},","\n",
                "        \"item2\":{\"value\":$input.json('$.item2')}","\n",
                "    }","\n",
                "}"
              ]]}
          },
          "IntegrationResponses" : [
            {
              "StatusCode" : "200",
              "ResponseParameters" : {
                "method.response.header.Access-Control-Allow-Origin":
                  {"Fn::Join":["",[
                      "'",
                      {"Ref":"AccessControlAllowOrigin"},
                      "'"
                    ]]}
              },
              "ResponseTemplates" : {
                "application/json": ""
              }
            },
            {
              "StatusCode" : "400",
              "SelectionPattern" : "[^2]\\d{2}",
              "ResponseParameters" : {
                "method.response.header.Access-Control-Allow-Origin":
                  {"Fn::Join":["",[
                      "'",
                      {"Ref":"AccessControlAllowOrigin"},
                      "'"
                    ]]}
              },
              "ResponseTemplates" : {
                "application/json": ""
              }
            }
          ]
        },
        "MethodResponses" : [
          {
            "StatusCode" : "200",
            "ResponseParameters" : {
              "method.response.header.Access-Control-Allow-Origin": false
            }
          },
          {
            "StatusCode" : "400",
            "ResponseParameters" : {
              "method.response.header.Access-Control-Allow-Origin": false
            }
          }
        ]
      }
    },
    "ApiMethodOPTIONS":{
      "Type" : "AWS::ApiGateway::Method",
      "Properties" : {
        "ResourceId" : {"Fn::GetAtt":["RestApi","RootResourceId"]},
        "RestApiId":{"Ref":"RestApi"},
        "HttpMethod" : "OPTIONS",
        "AuthorizationType" : "NONE",
        "ApiKeyRequired" : false,
        "Integration" : {
          "Type" : "MOCK",
          "RequestTemplates" : {
            "application/json":"{\"statusCode\":200}"
          },
          "IntegrationResponses" : [
            {
              "StatusCode" : "200",
              "ResponseParameters" : {
                "method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'",
                "method.response.header.Access-Control-Allow-Methods": "'POST,OPTIONS'",
                "method.response.header.Access-Control-Allow-Origin": {"Fn::Join":["",[
                      "'",
                      {"Ref":"AccessControlAllowOrigin"},
                      "'"
                  ]]}
              },
              "ResponseTemplates" : {
                "application/json":""
              }
            }
          ]
        },
        "MethodResponses" : [
          {
            "StatusCode" : "200",
            "ResponseParameters" : {
              "method.response.header.Access-Control-Allow-Headers": false,
              "method.response.header.Access-Control-Allow-Methods": false,
              "method.response.header.Access-Control-Allow-Origin": false
            },
            "ResponseModels" : {
              "application/json": "Empty"
            }
          }
        ]
      }
    },
    "ApiDeployment":{
      "DependsOn":[
        "ApiMethodPOST",
        "ApiMethodOPTIONS"
      ],
      "Type" : "AWS::ApiGateway::Deployment",
      "Properties" : {
        "RestApiId" : {"Ref":"RestApi"},
        "StageName" : {"Ref":"StageName"},
        "StageDescription":{
          "Variables" : {
            "subdomain":{"Ref":"KintoneSubdomain"},
            "apiToken":{"Ref":"KintoneApiToken"},
            "appId":{"Ref":"KintoneAppId"}
          }
        }
      }
    }
  },
  "Outputs":{
    "RestApiEndpoint":{
      "Value":{"Fn::Join":["",[
          "https://",
          {"Ref":"RestApi"},
          ".execute-api.",
          {"Ref":"AWS::Region"},
          ".amazonaws.com/",
          {"Ref":"StageName"},
          "/"
        ]]}
    }
  }
}

```

# 解説

- CloudFormation StackのParameterでKintone関連の情報を渡すと、StageVariablesに設定します。
- `Metadata`の部分は、ManagementConsoleでのParameter入力時の見た目を制御しています。
- 今回の例ではAPI Keyも認可も行っていないのでプリフライトリクエストでの`Access-Control-Allow-Headers`に使われないヘッダが含まれています。
- [APIGatewayのHTTP ProxyでKintoneのREST APIを呼ぶ（１）](http://qiita.com/monamu/items/a257a57e3236fca91ddc)でKitone側で日本語のフィールドコードがあると何か良くないことが起こるようなことを書きましたが、CloudFormationのtemplateで日本語をかけないので、`RequestTemplates`で詰みました。

# ブラウザから実行

## テスト用HTML

```html
<html>
<head>

</head>
<body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.2/jquery.min.js"></script>
<script>
  var settings = {
    "async": true,
    "crossDomain": true,
    "url": "https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/",
    "method": "POST",
    "headers": {
      "content-type": "application/json"
    },
    "processData": false,
    "data": "{\n    \"item1\":\"from Browser\",\n    \"item2\":\"item2\"\n}"
  }

  $.ajax(settings).done(function (response) {
    console.log(response);
  });
</script>
</body>
</html>
```

## 実行結果（ChromeのDeveloperToolより）

### OPTIONS （プリフライトリクエスト）

![image](https://qiita-image-store.s3.amazonaws.com/0/117772/a5ef4258-8b8c-93f0-67e6-ba2cb00b71f2.png)

### POST

![image](https://qiita-image-store.s3.amazonaws.com/0/117772/617ff993-6c76-9849-35f8-5a37b6587bdb.png)

### 結果

![image](https://qiita-image-store.s3.amazonaws.com/0/117772/93c84974-ef87-470c-1b70-dd6aafb91280.png)

（Kintone側は省略）

、、といとも簡単にできてしまいました。

僕は、パラメータも渡せてDeploy・ApiKeyに対応しているCloudFormationがSwaggerよりも好きです！（宣言）

では！