こんにちは、プロセスエンジニアリング部 プロセスエンジニアリング1課の中村です。
最近の悩みは、展示会やイベントの申込フォームの所属部署名欄が文字数制限で弾かれることです。
突然ですが、皆さんはこんなことを考えたことはありませんか?
自宅のWebサーバーのAPIを外から叩きたい…
でも、固定IPを割り振っているわけでもなく、DDNSも面倒くさい…VPNも嫌だ…
とはいえ、ngrokやserveoはセキュリティが怖い…
MQTT使えばグローバルIPアドレスを持ってない端末と通信できるけど、そもそもプロトコルが違うし、PublishとSubscribeはそれぞれ独立してるからPublishのResponseを同期で受け取れない…
そこで私はひらめきました。
AWS IoTをAPI Gatewayでラップして、MQTTのTopicを予めRequestとResponseのそれぞれで決めておけば、疑似的なHTTPリクエストっぽいことができるんじゃないか…!?
こんな具合です。怖いですね。LambdaからSubscribeなんてできるんでしょうか。
できました。というわけでサンプルのソースコードです。
mqttjsをラップして、リクエストクライアントぽくしてみました。
https://github.com/galactic1969/mqtt-async-requests
使い方は、Clientのインスタンスを生成して、doするだけです。doする際の引数で、TopicにUUIDを足すかどうか(Topicの衝突防止)、QoS、データが届くまでの待ち時間などを設定できるようにしています。
使い方は以下のサンプルを参考にしてみてください。Serverless FrameworkでAPI Gateway、Lambdaがデプロイされるようになっています。なお、このサンプルでは、HTTPのHeaderやMethodなどは考慮せず、単純にAPI GatewayからのGETリクエストを受け取り次第AWS IoTに決め打ちのテキストを送っています。認証も実装していませんので、ご利用の際にはご注意下さい。
https://github.com/galactic1969/mqtt-async-requests
動かしてみる
実際に動かしてみましょう。上記のサンプルを動かして時間を測定してみます。(今回は上の構成図の番号の、6, 7 を省略し、自宅のMQTT ClientでReceiveしたら即Publishするようにしています)
左側画面がMQTT Clientのログ、右側がcURLコマンドでHTTPリクエストを行っている様子です。
なんということでしょう…。MQTTブローカーとの接続だけでも1秒くらいかかるイメージだったのですが、HTTPリクエスト全体で0.2sec~0.4sec程度で返ってきました。流石にLambdaのコールドスタート時には1秒程度かかったのですが、それでも期待より早い数値でした。素晴らしいですね!あとはCognito認証なりCustom Authorizerを実装してあげれば、認証付きのAPIの出来上がりです!
とはいえ、今回の構成は多少無理がある構成です。ブローカーにAWS IoTを利用する場合は、次の制限値を気にしておく必要があります。
- ブローカーへの同時接続数: 500,000
- 1秒あたりのMQTT CONNECTリクエスト(アカウント単位): 500
- 1秒あたりのMQTT CONNECTリクエスト(ClientId単位): 1
- 1秒あたりのSUBSCRIBEリクエスト数(アカウント単位): 500
- アクティブなサブスクリプション数: 500,000
上記の中でも、1秒あたりのMQTT CONNETリクエスト数、SUBSCRIBEリクエスト数が重要です。今回の構成では、API Gatewayにリクエストが来るたびにCONNECT, SUBSCRIBEリクエストが飛ぶので、実質秒間500リクエストが上限のAPIになるといっても良いでしょう。それもアカウント単位の制限なので複数のAPIを実装する場合はより少なくなります。自宅のAPIをちょろっと呼ぶくらいに留めておいたほうが良いでしょう。ご利用は計画的に。
ちなみに、コネクションを残したままLambdaを終了できるかどうかも試してみましたが、偶数回の起動毎に例外が発生してしまい、できなさそうでした。残念。