AWS IoT Core に Amazon API Gateway からメッセージを Publish する方法
はじめに
AWS IoT Core にデータを publish する場合、よく使われるのは MQTTS プロトコルですが HTTPS でも実行が可能です。
また、Amazon API Gateway では、AWS サービス統合(AWS Service Proxy)により直接 AWS サービスへ REST API でリクエストすることができるので、もちろん AWS IoT Core へメッセージを Publish することもできます。
(2024 年 08 月現在で、HTTP API では AWS IoT Core は未サポートです)
よくある構成として、クラウド側(AWS IoT Core)からデバイスにメッセージを送りたい場合、Lambda を使って Publish することが多いですが、要件がシンプルな場合は API Gateway 経由で送ることで Lambda レスな構成にすることができます。
REST API の各種パラメーターと AWS IoT Core の HTTP エンドポイントの関係
AWS IoT Core に対してメッセージを Publish するときは、下記のフォーマット形式の HTTP メッセージ URL に POST します。
https://[IoT_data_endpoint]/topics/[url_encoded_topic_name]?qos=1
そのため、API Gateway からメッセージを送る時は、上記のパスに送ることができるようにパスを変換して渡す必要があります。
これは、API Gateway の「統合リクエスト」にある「アクションタイプ」の設定で「パスオーバーライド」を使う ことで実現できます。イメージとしては /foo/bar
というパスに対して API Gateway にメッセージを POST すると、topics/foo/bar
というパス形式に変換されて AWS IoT Core の HTTP メッセージ URL に POST されると考えると分かりやすいです。
パターン別設定方法
それでは具体的に設定しながら動作を見ていきたいと思います。今回は下記の 3 パターンの設定方法を紹介します。
- Publish するトピックが固定の場合
- Publish するトピックを URL パスで任意に指定する場合
- Publish するトピックを クエリ文字列で任意に指定する場合
今回は トピック sensor/temperature
という 2 階層の構造になっているトピックにメッセージを送る想定とします。
パターン 1. Publish するトピックが固定の場合
最初は一番単純な形です。あらかじめ Publish するトピックが決まっている場合、「URL パスパラメーター」 にトピック名をセットします。
sensor/temperature
というトピックに送るのでトピックの階層別にパスパラメーターを topic_level_1
と topic_level_2
と指定して、それぞれに sensor
と temperature
をセットします。
また、AWS IoT Core のエンドポイントに Publish する HTTP エンドポイントのフォーマットに合わせて、パスオーバーライドの設定を topics/{topic_level_1}/{topic_level_2}
とセットします。
実際のマネジメントコンソール上の 「結合リクエスト」 の設定画面です。
(今回は、/publish
の POST メソッドに対して設定しています)
IAM Role は 次のようなポリシーのものをセットします。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PostStaticTopic",
"Effect": "Allow",
"Action": [
"iot:Publish"
],
"Resource": [
"arn:aws:iot:[YOUR_REGION]:[YOUR_AWS_ACCOUNT_ID]:topic/sensor/temperature"
]
}
]
}
信頼関係は API Gateway から Publish するので次のようにします。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Sid": "",
"Principal": {
"Service": "apigateway.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
パターン 1 の CloudFormation テンプレート
CloudFormation テンプレートは以下のとおりです。
AWSTemplateFormatVersion: '2010-09-09'
Description: 'API Gateway with AWS IoT Core integration without mapping template'
Parameters:
AwsIotSubDomain:
Type: String
Default: ""
TopicLevel1:
Type: String
Default: sensor
TopicLevel2:
Type: String
Default: temperature
Qos:
Type: String
Default: "'1'"
Resources:
ApiGateway:
Type: 'AWS::ApiGateway::RestApi'
Properties:
Name: 'IoTCoreIntegrationApiStaticParameters'
Description: 'API for publishing messages to AWS IoT Core'
EndpointConfiguration:
Types:
- REGIONAL
ApiResource:
Type: 'AWS::ApiGateway::Resource'
Properties:
RestApiId: !Ref ApiGateway
ParentId: !GetAtt ApiGateway.RootResourceId
PathPart: 'publish'
ApiMethod:
Type: 'AWS::ApiGateway::Method'
Properties:
RestApiId: !Ref ApiGateway
ResourceId: !Ref ApiResource
HttpMethod: POST
AuthorizationType: NONE
Integration:
Type: AWS
IntegrationHttpMethod: POST
Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:${AwsIotSubDomain}.iotdata:path/topics/{topic_level_1}/{topic_level_2}'
Credentials: !GetAtt IoTRole.Arn
RequestParameters:
integration.request.path.topic_level_1: !Join ["", ["'", !Ref TopicLevel1, "'"]]
integration.request.path.topic_level_2: !Join ["", ["'", !Ref TopicLevel2, "'"]]
integration.request.querystring.qos: !Ref Qos
PassthroughBehavior: WHEN_NO_TEMPLATES
IntegrationResponses:
- StatusCode: 200
MethodResponses:
- StatusCode: 200
ApiDeployment:
Type: 'AWS::ApiGateway::Deployment'
DependsOn: ApiMethod
Properties:
RestApiId: !Ref ApiGateway
StageName: 'dev'
IoTRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: apigateway.amazonaws.com
Action: 'sts:AssumeRole'
Policies:
- PolicyName: IoTPublishPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: 'iot:Publish'
Resource: !Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/${TopicLevel1}/${TopicLevel2}'
Outputs:
ApiEndpoint:
Description: 'API Gateway endpoint URL for dev stage'
Value: !Sub 'https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/dev/publish'
パターン 1 の動作確認
curl
コマンドで手元の PC からリクエストを投げてみます。
curl -X POST "https://[YOUR-API-ID].execute-api.[YOUR-REGION].amazonaws.com/dev/publish?qos=1" -H "Content-Type: application/json" -d '{"data": "28.5"}'
AWS IoT Core 上のテストクライアントで sensor/temperature
をサブスクライブしていれば、下記のようにメッセージを受信できました。
API Gateway のコンソール上のテスト画面では、次のように「クエリ文字列」と「リクエスト本文」を指定する形になります。
テストを実施すると次のようなログを確認できたので、正常に IoT Core のエンドポイントにメッセージを送信できていることが分かります。
Endpoint request URI: https://[YOUR-IOT-CORE-ENDPOINT].iot.ap-northeast-1.amazonaws.com/topics/sensor/temperature?qos=1
パターン 2. Publish するトピックを URL パスで任意に指定する場合
次は、送信するトピックを都度指定して送る場合です。
最初に URL のリソースパスをトピックの階層の数だけ作成(/publish/{topic_path_1}/{topic_path_2}
)します。
一方で、AWS IoT Core のエンドポイントに Publish できる「HTTP エンドポイント」のフォーマットに合わせて、パスオーバーライドの設定を topics/{topic_level_1}/{topic_level_2}
とセットします。
また、URL パスパラメーター のtopic_level_1
と topic_level_2
のマッピング元は「メソッドリクエスト」のリクエストパスである topic_path_1
と topic_path_2
とするため、method.request.path.topic_path_1
と method.request.path.topic_path_2
を指定します。
- URL パスパラメータ
名前 | マッピング元 |
---|---|
topic_level_1 |
method.request.path.topic_path_1 |
topic_level_2 |
method.request.path.topic_path_2 |
- URL クエリ文字列パラメータ
名前 | マッピング元 |
---|---|
qos |
method.request.querystring.qos |
- リクエストパス
topic_path_1
topic_path_2
これにより、メッセージの POST 時に 2 階層のパスを任意に指定することで同じ階層のトピックにメッセージを Publish できます。トピックの階層構造は /
で区切るので URL パスと同じ形式になり分かりやすいかと思います。
パターン 2 の CloudFormation テンプレート
CloudFormation のテンプレートは以下のとおりです。
AWSTemplateFormatVersion: '2010-09-09'
Description: 'API Gateway with AWS IoT Core integration without mapping template'
Parameters:
AwsIotSubDomain:
Type: String
Default: ""
Resources:
ApiGateway:
Type: 'AWS::ApiGateway::RestApi'
Properties:
Name: 'IoTCoreIntegrationApiPath'
Description: 'API for publishing messages to AWS IoT Core'
EndpointConfiguration:
Types:
- REGIONAL
ApiResourcePublish:
Type: 'AWS::ApiGateway::Resource'
Properties:
RestApiId: !Ref ApiGateway
ParentId: !GetAtt ApiGateway.RootResourceId
PathPart: 'publish'
ApiResourceTopicLevel1:
Type: 'AWS::ApiGateway::Resource'
Properties:
RestApiId: !Ref ApiGateway
ParentId: !Ref ApiResourcePublish
PathPart: '{topic_path_1}'
ApiResourceTopicLevel2:
Type: 'AWS::ApiGateway::Resource'
Properties:
RestApiId: !Ref ApiGateway
ParentId: !Ref ApiResourceTopicLevel1
PathPart: '{topic_path_2}'
ApiMethod:
Type: 'AWS::ApiGateway::Method'
Properties:
RestApiId: !Ref ApiGateway
ResourceId: !Ref ApiResourceTopicLevel2
HttpMethod: POST
AuthorizationType: NONE
Integration:
Type: AWS
IntegrationHttpMethod: POST
Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:${AwsIotSubDomain}.iotdata:path/topics/{topic_level_1}/{topic_level_2}'
Credentials: !GetAtt IoTRole.Arn
RequestParameters:
integration.request.path.topic_level_1: method.request.path.topic_path_1
integration.request.path.topic_level_2: method.request.path.topic_path_2
integration.request.querystring.qos: method.request.querystring.qos
PassthroughBehavior: WHEN_NO_TEMPLATES
IntegrationResponses:
- StatusCode: 200
RequestParameters:
method.request.path.topic_path_1: true
method.request.path.topic_path_2: true
method.request.querystring.qos: true
MethodResponses:
- StatusCode: 200
ApiDeployment:
Type: 'AWS::ApiGateway::Deployment'
DependsOn: ApiMethod
Properties:
RestApiId: !Ref ApiGateway
StageName: 'dev'
IoTRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: apigateway.amazonaws.com
Action: 'sts:AssumeRole'
Policies:
- PolicyName: IoTPublishPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: 'iot:Publish'
Resource: !Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/*'
Outputs:
ApiEndpoint:
Description: 'API Gateway endpoint URL for dev stage'
Value: !Sub 'https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/dev/publish'
パターン 2 の動作確認
curl
コマンドで手元の PC からリクエストを投げてみます。
curl -X POST "https://[YOUR-API-ID].execute-api.[YOUR-REGION].amazonaws.com/dev/publish/sensor/temperature?qos=1" -H "Content-Type: application/json" -d '{"data": "20.0"}'
AWS IoT Core 上のテストクライアントで sensor/temperature
をサブスクライブしていれば、下記のようにメッセージを受信できました。
API Gateway のコンソール上のテスト画面は、次のように 2 つのパス topic_path_1
と topic_path_2
(とクエリ文字列 qos=1
)を指定する形になります。
テストを実行すると次のようなログを確認ました。指定したパスが正しく展開されていることが分かります。
Method request path: {topic_path_1=sensor, topic_path_2=temperature}
Method request query string: {qos=1}
Method request body before transformations: {"data": "20.0"}
Endpoint request URI: https://[YOUR-IOT-CORE-ENDPOINT].iot.ap-northeast-1.amazonaws.com/topics/sensor/temperature?qos=1
ちなみに今回は 2 階層のトピックに送る前提ですが、2 階層以上のトピックを使いたい場合は適宜 API Gateway の設定を追加してください。
パターン 3. Publish するトピックを クエリ文字列で任意に指定する場合
最後に、送信するトピックをクエリ文字列として送る場合です。
QoS と 2 階層分のトピックをクエリ文字列にセットしたいので、「メソッドリクエスト」は次のようにセットします。
- URL クエリ文字列パラメータ
qos
topic_querystring_1
topic_querystring_2
また、結合リクエストの「パスオーバーライド」はこれまでと同じ topics/{topic_level_1}/{topic_level_2}
として、各パスパラメータ({topic_level_1}
と {topic_level_2}
)を先程のクエリ文字列にマッピングしたいので、パスパラメータを次のように設定します。
- URL パスパラメータ
名前 | マッピング元 |
---|---|
topic_level_1 |
method.request.querystring.topic_querystring_1 |
topic_level_2 |
method.request.querystring.topic_querystring_2 |
- URL クエリ文字列パラメータ
名前 | マッピング元 |
---|---|
qos |
method.request.querystring.qos |
パターン 3 の CloudFormation テンプレート
この CloudFormation テンプレートは以下のとおりです。
AWSTemplateFormatVersion: '2010-09-09'
Description: 'API Gateway with AWS IoT Core integration without mapping template'
Parameters:
AwsIotSubDomain:
Type: String
Default: ""
Resources:
ApiGateway:
Type: 'AWS::ApiGateway::RestApi'
Properties:
Name: 'IoTCoreIntegrationApiQueryString'
Description: 'API for publishing messages to AWS IoT Core'
EndpointConfiguration:
Types:
- REGIONAL
ApiResource:
Type: 'AWS::ApiGateway::Resource'
Properties:
RestApiId: !Ref ApiGateway
ParentId: !GetAtt ApiGateway.RootResourceId
PathPart: 'publish'
ApiMethod:
Type: 'AWS::ApiGateway::Method'
Properties:
RestApiId: !Ref ApiGateway
ResourceId: !Ref ApiResource
HttpMethod: POST
AuthorizationType: NONE
Integration:
Type: AWS
IntegrationHttpMethod: POST
Uri: !Sub 'arn:aws:apigateway:${AWS::Region}:${AwsIotSubDomain}.iotdata:path/topics/{topic_level_1}/{topic_level_2}'
Credentials: !GetAtt IoTRole.Arn
RequestParameters:
integration.request.path.topic_level_1: method.request.querystring.topic_querystring_1
integration.request.path.topic_level_2: method.request.querystring.topic_querystring_2
integration.request.querystring.qos: method.request.querystring.qos
PassthroughBehavior: WHEN_NO_TEMPLATES
IntegrationResponses:
- StatusCode: 200
RequestParameters:
method.request.querystring.qos: true
method.request.querystring.topic_querystring_1: true
method.request.querystring.topic_querystring_2: true
MethodResponses:
- StatusCode: 200
ApiDeployment:
Type: 'AWS::ApiGateway::Deployment'
DependsOn: ApiMethod
Properties:
RestApiId: !Ref ApiGateway
StageName: 'dev'
IoTRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: apigateway.amazonaws.com
Action: 'sts:AssumeRole'
Policies:
- PolicyName: IoTPublishPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action: 'iot:Publish'
Resource: !Sub 'arn:aws:iot:${AWS::Region}:${AWS::AccountId}:topic/*'
Outputs:
ApiEndpoint:
Description: 'API Gateway endpoint URL for dev stage'
Value: !Sub 'https://${ApiGateway}.execute-api.${AWS::Region}.amazonaws.com/dev/publish'
パターン 3 の動作確認
curl
コマンドで手元の PC からリクエストを投げてみます。
curl -X POST "https://[YOUR-API-ID].execute-api.[YOUR-REGION].amazonaws.com/dev/publish/?topic_querystring_1=sensor&topic_querystring_2=temperature&qos=1" -H "Content-Type: application/json" -d '{"data": "25.5"}'
AWS IoT Core 上のテストクライアントで sensor/temperature
をサブスクライブしていれば、下記のようにメッセージを受信できました。
API Gateway のコンソール上のテスト画面では、次のようにクエリ文字列を指定する形になります。
topic_querystring_1=sensor&topic_querystring_2=temperature&qos=1
テストを実行すると次のようなログを確認ました。指定したクエリ文字列が正しく変換されていることが分かります。
Method request path: {}
Method request query string: {topic_querystring_2=temperature, qos=1, topic_querystring_1=sensor}
Method request body before transformations: {"data": "25.5"}
Endpoint request URI: https://[YOUR-IOT-Core-ENDPOINT].iot.ap-northeast-1.amazonaws.com/topics/sensor/temperature?qos=1
最後に
API Gateway から IoT Core に対するメッセージの送信方法を 3 パターン見てきました。送り方はそれぞれ違えど最終的にすべて同じ IoT Core の HTTP エンドポイント [IoT Core Endpoint]/topics/sensor/temperature?qos=1
に送れました。
利用頻度は多くないかもしれませんが、マッチしそうなユースケースがあればぜひ活用していただければと思います。
参考 URL