この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
AWS CLI が 2015/10/26 リリースのバージョン 1.9.0 から API Gateway に対応しました。
API Gateway が発表されたのは 2015 年 7 月ですので、CLI のリリースまでに三ヶ月以上を要しました。 CLI 対応記念として、getting started ドキュメントにある次のチュートリアルを AWS CLI から実行してみます。
演習: Lambda 関数 - API Gateway を使用して、カスタム API を作成し、一連の AWS Lambda 関数に接続した後に、API から Lambda 関数を呼び出します。
AWS CLI は生の REST API をガシガシ叩くだけですので、よりアプリケーション開発者フレンドリーに API Gateway&Lambda で開発したい方は、素直に JAWS フレームワークなどをご検討ください。
チュートリアルのゴール
「MyDemoAPI」という API Gateway を用意し、パス(リソース)「/mydemoresource」に GET/POST メソッドを定義します。
GET メソッドは Lambda 関数「GetHelloWorld」を、 POST メソッドは Lambda 関数「GetHelloWithName」 を invoke します。
作成した API を test ステージにデプロイし 定義した GET/POST メソッドにリクエストします。
以下の流れで作業します。
- AWS CLI をインストールする
- API を作成する
- リソースを作成する
- Lambda 関数を作成する
- GET メソッドを作成してテストする
- POST メソッドを作成してテストする
- API をデプロイする
- API をテストする
- クリーンアップ
注意事項
API Gateway を AWS CLI から操作するとハマるかもしれない箇所を3点ほど先に述べます。
一つ目。API Gateway の REST API は Hypertext Application Language(HAL) がベースになっており、これまでのような XML ではなく JSON でやり取りします(HAL 自体は XML もサポートします)。 AWS CLI の HAL サポートはまだこなれていないので、細かい動作が胡散臭いです。 暖かく見守ってください(すでにバグを3つほど踏抜きました)。
二つ目。今回のチュートリアルでは CLIENT -> API Gateway -> Lambda と連携します。
CLIENT -> API Gateway のメソッド(--http-method
)によらず、API Gateway は POST メソッド(--integration-http-method
)で Lambda 関数を invoke します。
三つ目。API Gateway が Lambda を invoke できるように Lambda 関数のパーミッションを設定しましょう。 パーミッションを設定し忘れると "Execution failed due to configuration error: Invalid permissions on Lambda function" というエラーが発生します。 マネージメントコンソールから操作する際は、パーミッション設定の確認ダイアログが表示されます。
それでは本題に戻ります。
1. AWS CLI をインストールする
AWS CLI からコマンド実行するため、なにはともあれ AWS CLI をインストールします。 AWS CLI はパッケージ管理ツールの pip からインストールします。
$ pip install awscli --upgrade
$ aws --version
aws-cli/1.9.2 Python/2.7.10 Darwin/14.5.0 botocore/1.3.2
AWS CLI のバージョンが少なくとも 1.9.2 以上であることを確認してください。
API Gateway のサービス名は apigateway です。
$ aws apigateway help
を叩くと、大量のコマンド一覧のヘルプ画面が出てくるはずです。
$ aws apigateway help
APIGATEWAY() APIGATEWAY()
apigateway -
Amazon API Gateway helps developers deliver robust, secure and scalable
mobile and web application backends. Amazon API Gateway allows develop-
ers to securely connect mobile and web applications to APIs that run on
AWS Lambda, Amazon EC2, or other publicly addressable web services that
are hosted outside of AWS.
o create-api-key
o create-base-path-mapping
o create-deployment
o create-domain-name
o create-model
o create-resource
o create-rest-api
o create-stage
o delete-api-key
o delete-base-path-mapping
o delete-client-certificate
o delete-deployment
o delete-domain-name
o delete-integration
o delete-integration-response
o delete-method
o delete-method-response
o delete-model
o delete-resource
o delete-rest-api
o delete-stage
o flush-stage-cache
o generate-client-certificate
o get-account
o get-api-key
o get-api-keys
o get-base-path-mapping
o get-base-path-mappings
o get-client-certificate
o get-client-certificates
o get-deployment
o get-deployments
o get-domain-name
o get-domain-names
o get-integration
o get-integration-response
o get-method
o get-method-response
o get-model
o get-model-template
o get-models
o get-resource
o get-resources
o get-rest-api
o get-rest-apis
o get-sdk
o get-stage
o get-stages
o help
o put-integration
o put-integration-response
o put-method
o put-method-response
o test-invoke-method
o update-account
o update-api-key
o update-base-path-mapping
o update-client-certificate
o update-deployment
o update-domain-name
o update-integration
o update-integration-response
o update-method
o update-method-response
o update-model
o update-resource
o update-rest-api
o update-stage
APIGATEWAY()
help コマンドをのぞいても 67 個あります。賑やかですね。
コマンドリファレンスはこちらです http://docs.aws.amazon.com/cli/latest/reference/apigateway/index.html
2. API を作成する
API の作成は create-rest-api API を使います。 API 名は "MyDemoAPI" です。
$ aws apigateway create-rest-api --name MyDemoAPI --description "This is my API for demonstration purposes"
{
"id": "asdfqwer",
"name": "MyDemoAPI",
"description": "This is my API for demonstration purposes",
"createdDate": 1446272225
}
$ aws apigateway get-rest-apis # API 一覧を確認
{
"items": [
{
"description": "This is my API for demonstration purposes",
"createdDate": 1446272225,
"id": "asdfqwer",
"name": "MyDemoAPI"
}
]
}
$ REST_API_ID="asdfqwer"
なお name が重複していても作成可能です。
最終的に API は REST_API_ID.execute-api.REGION.amazonaws.com のホスト名でアクセスすることになります。
3. リソースを作成する
API 作成直後はルート(/)リソースしか存在しません。
$ aws apigateway get-resources --rest-api-id $REST_API_ID
{
"items": [
{
"path": "/",
"id": "xzw05h7r0h"
}
]
}
$ ROOT_ID="xzw05h7r0h"
ルート直下にリソース mydemoresource を追加します。
リソースの作成は create-resource API を使います。
親となるリソースルートを --parent-id
で指定します。
$ aws apigateway create-resource --rest-api-id $REST_API_ID --parent-id $ROOT_ID --path-part "mydemoresource"
{
"path": "/mydemoresource",
"pathPart": "mydemoresource",
"id": "zzzzzz",
"parentId": "xxxxxxxx"
}
$ aws apigateway get-resources --rest-api-id $REST_API_ID
{
"items": [
{
"path": "/",
"id": "xxxxxxxx"
},
{
"path": "/mydemoresource",
"id": "zzzzzz",
"pathPart": "mydemoresource",
"parentId": "xxxxxxxx"
}
]
}
マネジメントコンソールと異なり、path
と pathPart
を個別に指定できないようです。
4. Lambda 関数を作成する
チュートリアルに従い、Lambda 関数を2つ(GetHelloWithName と GetHelloWithName)作成してください。
$ aws lambda list-functions
{
"Functions": [
{
...
"FunctionName": "GetHelloWithName",
"FunctionArn": "arn:aws:lambda:ap-northeast-1:1234567890:function:GetHelloWithName",
...
},
{
...
"FunctionName": "GetHelloWorld",
"FunctionArn": "arn:aws:lambda:ap-northeast-1:1234567890:function:GetHelloWorld",
...
},
]
}
FunctionArn
はあとで利用します。
5. GET メソッドを作成してテストする
リソース mydemoresource
に GET すると、Lambda 関数 GetHelloWorld
を呼び出すように GET メソッドを定義します。
マネージメントコンソールでは次の画面に相当します。
- Method Request(クライアントから API Gateway へのリクエスト)
- put-integration(API Gateway から Lambda へのリクエスト)
- put-integration-response(Lambda から API Gateway へのレスポンス)
- put-method-response(API Gateway からクライアントへのレスポンス)
という4項目をそれぞれ AWS CLI から定義します。
Black Blet Tech シリーズ Amazon API Gateway の P.49 から先も合わせてご確認ください。
Method Request(クライアントから API Gateway へのリクエスト)
リソース /mydemoresource に対して GET メソッドを用意します(--http-method GET
)。
認証なしでリクエストできるようにするため --authorization-type None
とします。
http://docs.aws.amazon.com/cli/latest/reference/apigateway/put-method.html
$ aws apigateway put-method \
--rest-api-id $REST_API_ID \
--resource-id $RESOURCE_ID \
--http-method GET \
--authorization-type NONE \
--no-api-key-required \
--request-parameters {}
{
"apiKeyRequired": false,
"httpMethod": "GET",
"authorizationType": "NONE",
"requestParameters": {}
}
put-integration(API Gateway から Lambda へのリクエスト)
- API Gateway から Lambda 関数へのリクエストを定義します。
- インテグレーション先が AWS のサービスのため、
--type AWS
とします。 - API Gateway から Lambda 関数へは POST でリクエストします (
--integration-http-method POST
)。 Lambda 関数は--uri
で指定します。
uri は "arn:aws:apigateway:REGION:lambda:path/2015-03-31/functions/LAMBDA_FUNCTION_ARN/invocations"
という形をしていて、 具体的には "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:1234567890:function:GetHelloWorld/invocations"
というようになります。
$ aws apigateway put-integration \
--rest-api-id $REST_API_ID \
--resource-id $RESOURCE_ID \
--http-method GET \
--integration-http-method POST \
--type AWS \
--uri "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:1234567890:function:GetHelloWorld/invocations"
{
"httpMethod": "POST",
"type": "AWS",
"uri": "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:1234567890:function:GetHelloWorld/invocations",
"cacheNamespace": "zzzzzz"
}
put-method-response(API Gateway からクライアントへのレスポンス)
API Gateway からクライアントへのレスポンスを定義します。
レスポンスは JSON 形式のため --response-models '{"application/json": "Empty"}'
とします。
"Empty" はモデルの名前です。
モデルは API ごとに存在し CLI からは $ aws apigateway get-models --rest-api-id $REST_API_ID
で確認出来ます。
$ aws apigateway put-method-response \
--rest-api-id $REST_API_ID \
--resource-id $RESOURCE_ID \
--http-method GET \
--status-code 200 \
--response-models '{"application/json": "Empty"}'
{
"responseModels": {
"application/json": "Empty"
},
"statusCode": "200"
}
put-integration-response(Lambda から API Gateway へのレスポンス)
Lambda から API Gateway へのレスポンスを定義します。
API Gateway ではテンプレートと言う概念があり、Lambda 関数のレスポンスに対してスキーマ変換出来ます。
http://docs.aws.amazon.com/apigateway/latest/developerguide/models-mappings.html
--response-templates
引数でこのテンプレートを指定します。
今回はスキーマ変換せず、 JSON をそのまま返すので --response-templates '{"application/json": null}'
としたいところなのですが、AWS CLI にバグが あるため、ワークアラウンドとして --response-templates '{"application/json": ""}'
と空文字を指定します。
BUG URL https://github.com/aws/aws-cli/issues/1610
$ aws apigateway put-integration-response \
--rest-api-id $REST_API_ID \
--resource-id $RESOURCE_ID \
--http-method GET \
--status-code 200 \
--response-templates '{"application/json": null}'
Parameter validation failed:
Invalid type for parameter responseTemplates.application/json, value: None, type: <type 'NoneType'>, valid types: <type 'basestring'>
# workaround
$ aws apigateway put-integration-response \
--rest-api-id $REST_API_ID \
--resource-id $RESOURCE_ID \
--http-method GET \
--status-code 200 \
--response-templates '{"application/json": ""}'
{
"statusCode": "200",
"responseTemplates": {
"application/json": null
}
}
Lambda 関数のパーミッション変更
最後に、API Gateway が Lambda 関数を invoke できるように Lambda 関数のパーミッションを変更します。
lambda add-permission API を使います。
--action
は"lambda:InvokeFunction"
--principal
はapigateway.amazonaws.com
--source-arn
は"arn:aws:execute-api:ap-northeast-1:ACOUNT_ID:API_ID/*/HTTP_METHOD/RESOURCE_NAME"
とします。
$ aws lambda add-permission --function-name GetHelloWorld \
--statement-id f3385cd3-c2f8-49e5-ba72-d2801e5f93d8 \
--action "lambda:InvokeFunction" \
--principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:ap-northeast-1:1234567890:asdfqwer/*/GET/mydemoresource"
{
"Statement": "{\"Condition\":{\"ArnLike\":{\"AWS:SourceArn\":\"arn:aws:execute-api:ap-northeast-1:1234567890:asdfqwer/*/GET/mydemoresource\"}},\"Action\":[\"lambda:InvokeFunction\"],\"Resource\":\"arn:aws:lambda:ap-northeast-1:1234567890:function:GetHelloWorld\",\"Effect\":\"Allow\",\"Principal\":{\"Service\":\"apigateway.amazonaws.com\"},\"Sid\":\"f3385cd3-c2f8-49e5-ba72-d2801e5f93d8\"}"
}
$ aws lambda get-policy --function-name GetHelloWorld | jq -r .Policy | jq .
{
"Version": "2012-10-17",
"Statement": [
{
"Condition": {
"ArnLike": {
"AWS:SourceArn": "arn:aws:execute-api:ap-northeast-1:1234567890:asdfqwer/*/GET/mydemoresource"
}
},
"Action": "lambda:InvokeFunction",
"Resource": "arn:aws:lambda:ap-northeast-1:1234567890:function:GetHelloWorld",
"Effect": "Allow",
"Principal": {
"Service": "apigateway.amazonaws.com"
},
"Sid": "f3385cd3-c2f8-49e5-ba72-d2801e5f93d8"
}
],
"Id": "default"
}
GET メソッドのテスト実行
ここで定義した mydemoresource
リソースの GET メソッドを確認します。
まずは定義を確認確認します。
$ aws apigateway get-method --rest-api-id $REST_API_ID --resource-id $RESOURCE_ID --http-method GET
{
"apiKeyRequired": false,
"httpMethod": "GET",
"methodIntegration": {
"integrationResponses": {
"200": {
"responseTemplates": {
"application/json": null
},
"statusCode": "200"
}
},
"cacheKeyParameters": [],
"uri": "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:1234567890:function:GetHelloWorld/invocations",
"httpMethod": "POST",
"cacheNamespace": "zzzzzz",
"type": "AWS"
},
"requestParameters": {},
"methodResponses": {
"200": {
"responseModels": {
"application/json": "Empty"
},
"statusCode": "200"
}
},
"authorizationType": "NONE"
}
GET メソッドを実際に呼び出しましょう。
$ aws apigateway test-invoke-method --rest-api-id $REST_API_ID --resource-id $RESOURCE_ID --http-method GET --path-with-query-string ''
{
"status": 200,
"body": "{\"Hello\":\"World\"}",
"log": "Execution log for request test-request...[snip]...Successfully completed execution\n",
"latency": 1111,
"headers": {
"Content-Type": "application/json"
}
}
status が 200 で body の内容から期待どおりのレスポンスがかえってきています。
6. POST メソッドを作成してテストする
GET メソッドと同じ要領でリソース mydemoresource
に POST すると Lambda 関数 GetHelloWithName
を呼び出すように POST メソッドを定義します。
- Method Request(クライアントから API Gateway へのリクエスト)
- put-integration(API Gateway から Lambda へのリクエスト)
- put-integration-response(Lambda から API Gateway へのレスポンス)
- put-method-response(API Gateway からクライアントへのレスポンス)
という4項目をそれぞれ AWS CLI から定義します。
Black Blet Tech シリーズ Amazon API Gateway の P.49 から先も合わせてご確認ください。
Method Request(クライアントから API Gateway へのリクエスト)
リソース /mydemoresource に対して POST メソッドを用意します。(--http-method POST) 認証なしでリクエストできるようにするため --authorization-type None とします。
$ aws apigateway put-method \
--rest-api-id $REST_API_ID \
--resource-id $RESOURCE_ID \
--http-method POST \
--authorization-type NONE \
--no-api-key-required \
--request-parameters {}
{
"apiKeyRequired": false,
"httpMethod": "POST",
"authorizationType": "NONE",
"requestParameters": {}
}
put-integration(API Gateway から Lambda へのリクエスト)
GET の時と同じです。
$ aws apigateway put-integration \
--rest-api-id $REST_API_ID \
--resource-id $RESOURCE_ID \
--http-method POST \
--integration-http-method POST \
--type AWS \
--uri "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:1234567890:function:GetHelloWithName/invocations"
{
"httpMethod": "POST",
"type": "AWS",
"uri": "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:1234567890:function:GetHelloWithName/invocations",
"cacheNamespace": "zzzzzz"
}
put-integration-response(Lambda から API Gateway へのレスポンス)
GET の時と同じです。
$ aws apigateway put-method-response \
--rest-api-id $REST_API_ID \
--resource-id $RESOURCE_ID \
--http-method POST \
--status-code 200 \
--response-models '{"application/json": "Empty"}'
{
"responseModels": {
"application/json": "Empty"
},
"statusCode": "200"
}
put-method-response(API Gateway からクライアントへのレスポンス)
GET の時と同じです。
$ aws apigateway put-integration-response \
--rest-api-id $REST_API_ID \
--resource-id $RESOURCE_ID \
--http-method POST \
--status-code 200 \
--response-templates '{"application/json": ""}'
{
"statusCode": "200",
"responseTemplates": {
"application/json": null
}
}
Lambda 関数のパーミッション変更
最後に、API Gateway が Lambda 関数を invoke できるように Lambda 関数のパーミッションを変更します。
GET の時とは
--function-name
(Lambda 関数名)--source-arn
(GET から POST への変更)
が変わっています。
$ aws lambda add-permission --function-name GetHelloWithName \
--statement-id 72e0a706-02e8-479f-affb-0a2dcc5d4a29 \
--action "lambda:InvokeFunction" \
--principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:ap-northeast-1:1234567890:asdfqwer/*/POST/mydemoresource"
POST メソッドのテスト実行
ここで定義した mydemoresource リソースの POST メソッドを確認します。
$ aws apigateway get-method --rest-api-id $REST_API_ID --resource-id $RESOURCE_ID --http-method POST
{
"apiKeyRequired": false,
"httpMethod": "POST",
"methodIntegration": {
"integrationResponses": {
"200": {
"responseTemplates": {
"application/json": null
},
"statusCode": "200"
}
},
"cacheKeyParameters": [],
"uri": "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:1234567890:function:GetHelloWithName/invocations",
"httpMethod": "POST",
"cacheNamespace": "zzzzzz",
"type": "AWS"
},
"requestParameters": {},
"methodResponses": {
"200": {
"responseModels": {
"application/json": "Empty"
},
"statusCode": "200"
}
},
"authorizationType": "NONE"
}
POST メソッドを実際に呼び出しましょう。
POST リクエスト時のボディーは --body
で渡します。
$ aws apigateway test-invoke-method \
--rest-api-id $REST_API_ID \
--resource-id $RESOURCE_ID \
--http-method POST \
--path-with-query-string '' \
--body '{"name":"John"}'
{
"status": 200,
"body": "{\"Hello\":\"John\"}",
"log": "Execution log for request test-request...Successfully completed execution\n",
"latency": 69,
"headers": {
"Content-Type": "application/json"
}
}
レスポンスの body が {"Hello":"John"} となっており、POST リクエスト内容が反映されています。
7. API をデプロイする
定義したAPIを "test" ステージにデプロイします。
https://REST_API_ID.execute-api.REGION.amazonaws.com/STAGE_NAME/RESOURCE_NAME
の形式でアクセスできるようになります。
$ aws apigateway create-deployment \
--rest-api-id $REST_API_ID \
--stage-name test \
--stage-description "This is a test" \
--description "Calling Lambda functions walkthroug"
{
"description": "Calling Lambda functions walkthroug",
"id": "aaaaaa",
"createdDate": 1446288905
}
$ aws apigateway get-deployments --rest-api-id $REST_API_ID
{
"items": [
{
"createdDate": 1446288905,
"id": "aaaaaa",
"description": "Calling Lambda functions walkthroug"
}
]
}
$ aws apigateway get-stages --rest-api-id $REST_API_ID
{
"item": [
{
"description": "This is a test",
"stageName": "test",
"cacheClusterEnabled": false,
"cacheClusterStatus": "NOT_AVAILABLE",
"deploymentId": "aaaaaa",
"lastUpdatedDate": 1446288905,
"createdDate": 1446288905,
"methodSettings": {}
}
]
}
8. API をテストする
curl から GET/POST それぞれでリソース mydemoresource
にアクセスします。
GET メソッド
$ curl https://$REST_API_ID.execute-api.ap-northeast-1.amazonaws.com/test/mydemoresource
{"Hello":"World"}
POST メソッド
$ curl -H "Content-Type: application/json" -X POST -d "{\"name\": \"John\"}" https://$REST_API_ID.execute-api.ap-northeast-1.amazonaws.com/test/mydemoresource
{"Hello":"John"}
期待通りですね。
9. クリーンアップ
最後に API を削除します。
$ aws apigateway delete-rest-api --rest-api-id $REST_API_ID
$ aws apigateway get-rest-apis
{
"items": []
}
Hypertext Application Language(HAL) について
API Gateway の REST API Reference にあるように、この API は JSON HAL をしゃべります。
The Amazon API Gateway web service is a resource-based API that uses Hypertext Application Language (HAL). http://docs.aws.amazon.com/apigateway/api-reference/
試しにcreate-rest-api
API を --debug オプション付きで実行してみましょう。
$ aws apigateway create-rest-api --name dummy --debug
...
2015-10-31 19:59:18,003 - MainThread - botocore.vendored.requests.packages.urllib3.connectionpool - DEBUG - "POST /restapis HTTP/1.1" 201 120
2015-10-31 19:59:18,004 - MainThread - botocore.parsers - DEBUG - Response headers: {'x-amzn-requestid': '6c872d07-7fbe-11e5-bcdc-93557855b8e7', 'date': 'Sat, 31 Oct 2015 10:59:14 GMT', 'content-length': '120', 'content-type': 'application/json'}
2015-10-31 19:59:18,004 - MainThread - botocore.parsers - DEBUG - Response body:
{"createdDate":1446289154,"description":"This is my API for demonstration purposes","id":"fdp48380xl","name":"DemoAPI"}
...
確かに
- HTTP ステータス
201:Created
'content-type': 'application/json'
- レスポンスボディー
{"createdDate":1446289154,"description":"This is my API for demonstration purposes","id":"fdp48380xl","name":"DemoAPI"}
という JSON が返ってきていますね。
これまでの XML との決裂をわかった上で API Gateway だけ独自路線で HAL をねじ込んできた API Gateway のプロダクトマネージャーやアーキテクトは相当なやり手と思われ、周りを巻き込んだリリースまでの道のりについてどこかで語って欲しいですね。
まとめ
生 REST API は辛い。