アプリケーション構築を学習する一環として、RSSフィードで配信された記事をLINE Messaging API経由で通知するアプリをServerless Frameworkで作りました。
新着記事を定期配信したり、手動で取得したりすることができるアプリです。
成果物はGithubにアップロードしていますが、せっかくなのでアプリの概要と、ハマったポイントをこちらにも投稿しておこうと思います。(以下、Github上のREADME.mdのほぼ抜粋です)
概要
- RSSFeedで配信された記事を、LINE Messaging API経由で購読する
- 購読対象の記事は、設定用サイトで追加・削除・有効化・無効化が可能
- 定期購読のほかに、LINEでメッセージを送ることで記事を手動購読することも可能
- システム構成は以下の通り。AWS上にソースをデプロイし、LINE Messaging API・LIFFと連携させる。
環境
種類 | バージョン |
---|---|
OS | Windows10 version 2004 |
Node | 14.17.5 |
Serverless Framework | 2.66.1 |
開発環境 | VSCode & Powershell |
ソース
Githubにアップしています。
使ったサービス・言語・フレームワーク
サービス・言語等 | 詳細 |
---|---|
言語・環境 | バックエンドのロジックはすべてNode.js v14.17.5で構築。 |
フレームワーク | フレームワークはServerless Framework v2.66.2を用いて、AWS上サービスをデプロイした。 また、いくつかプラグインを導入した(※1)。 |
LINE Messaging API | LINE上でのメッセージを受信し、適切な応答をするためにLINE Messaging APIを導入。無料枠で利用するので、月1,000件のメッセージ送信数制限には要注意・・・。応答はFlex Message(カルーセル型)もしくはテキスト形式で実施。 |
LIFF | LINE Front-end Framework (LIFF)を使って、設定用のWebサイトで、LINEログインを利用しユーザID・名前・プロフィール画像を取得。 |
データベース | AWS DynamoDBにユーザごとの情報(※2)を格納。 |
UI | React + React-Bootstrap + Fontawesomeで設定画面のUIを構築。 |
※1 利用したServerless Frameworkのプラグインは以下の通り。
plugins:
- serverless-dynamodb-local #DynamoDB接続部分の開発・テスト用
- serverless-vpc-plugin #Lambda用のVPC作成
- serverless-offline #ローカルでの開発・テスト
- serverless-layers #Lambda Layersを利用するためのプラグイン
plugins:
- serverless-s3-sync #ビルドしたフロントエンドのS3アップロード
- serverless-cloudfront-invalidate #CloudFrontのキャッシュ削除自動化
※2 DynamoDBレコードの構成は以下の通り。
-
userId
:ユーザID -
lastSubscribe
:RSSフィードの最終配信日時 -
subscribeFeeds
:購読するRSSフィード情報(ここではPublicKeyとDevelopersIOの2つ) -
name
:ユーザ名
{
"userId": "U0000000000000000000000000000000",
"lastSubscribe": "2021-11-25T01:00:19.091Z",
"subscribeFeeds": [
{
"feedUrl": "https://www.publickey1.jp/atom.xml",
"addedAt": "2021-11-22T12:23:51.240Z",
"siteUrl": "https://www.publickey1.jp/",
"lastModifiedAt": "2021-11-21T23:23:51.240Z",
"lastAction": "added",
"feedId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"title": "Publickey",
"enabled": true
},
{
"feedUrl": "https://dev.classmethod.jp/feed/",
"addedAt": "2021-11-22T05:24:35.168Z",
"siteUrl": "https://dev.classmethod.jp",
"lastModifiedAt": "2021-11-21T23:24:35.168Z",
"lastAction": "added",
"feedId": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"title": "DevelopersIO",
"enabled": true
}
],
"name": "XXX"
}
ハマったところ
Missing Authentication Tokenエラー
開発中、sls offline start
でローカル開発環境では問題なくバックエンドAPIが動作するのに、AWSにデプロイするとMissing Authentication Token
エラーが返されるようになった。
特に認証トークンを求めるような仕様にはしてないはず・・・ということで調べてみると、公式回答曰く認証トークンがない時だけでなく存在しないリソースやメソッドにリクエストを投げた時もこのレスポンスになるとのこと。(だったら、いかにも認証トークンエラーっぽい文言にしないでほしいですが・・・)
ただ、メソッドやリソースが存在しないといってもローカル開発環境では問題なく動作するし・・・と色々調べたところ、実は当時serverless.yml
に設定していた下記が誤っていることが判明。
functions:
webhook:
handler: functions/webhook.handler
events:
- http:
method: ANY
path: /webhook/{proxy+} #<=この部分
この設定を書いたとき、https://xxxxxxx/webhook/xxx-xxx-1234{uuid}
のように、パスパラメータ付きでリクエストすることも想定していたのですが、どうも{proxy+}は空白はNGのようでした。ただ、serverless-offline
を使った開発環境では空白でも動作してしまうので、混乱してしまいました・・・。
改めて、下記のようにしたら動作することを確認。
functions:
webhook:
handler: functions/webhook.handler
events:
- http:
method: ANY
path: /webhook/{proxy+}
- http: #追加
method: ANY #追加
path: /webhook #追加
その後、アプリ構築を進める中でパスパラメータを使うことがないと分かったので、現在の形で決着。
functions:
webhook:
handler: functions/webhook.handler
events:
- http:
method: ANY
path: "/webhook"
https化
LIFFが連携するURL(Endpoint URL)が、httpsサイトでないと設定できないことが途中で判明。
本番環境については、S3にホスティングしているURLを直接指定するのではなく、Cloudfrontを追加でデプロイすることで、CloudfrontのURLはhttps化しているためそちらを指定することで解決した。
開発環境については、yarn start
ではどうしてもhttp://localhost:3000
になってしまうと思っていたが、こちらのサイトを参照し.env.development.local
でHTTPS = "true"
を定義することで簡単にhttps化できることを知り、それで解決できた。
自分でオレオレ証明書を発行して・・・とそれなりに面倒な手順を踏もうかと思っていたので、助かりました。
outbound80の手動穴あけ
RSSフィード配信サイトに情報を取りに行く際、LambdaがインターネットアクセスするためにVPC・NAT Gateway等の定義をする必要があったが、それらを簡単に実施するためにserverless-vpc-plugin
を使って開発していた。
その後、Lambda上でテストをする中でRSSフィードをうまく取得できるサイトと、取得できないサイトがあることがわかった。
原因を調べていくとserverless-vpc-plugin
で構築した中でLambdaに割り当てられるセキュリティグループは、インターネットへのアウトバウンドアクセスが443(https)しか許可されておらず、"http://~"の配信サイトにアクセスできていないことが判明(特に、画像データだけはhttps→httpにリダイレクトして配信しているようなサイトもあり、なかなか検知しづらかったです・・・)。
serverless.yml
上に自分でVPC等のリソースを定義しようかとも考えたが、結局deploy.ps1
上でLambda実行用のセキュリティグループにOutbound 80ポートの穴あけを追加で定義することで解決しました。
#deploy.ps1抜粋
$TEMP_SG = aws cloudformation describe-stacks --output text --stack-name feed-notify-dev --query 'Stacks[].Outputs[?OutputKey==`AppSecurityGroupId`].[OutputValue]'
aws ec2 authorize-security-group-egress --group-id $TEMP_SG --ip-permissions IpProtocol=tcp, FromPort=80, ToPort=80, IpRanges='[{CidrIp=0.0.0.0/0,Description="HTTP ACCESS for RSS Feed Subscribe"}]'
Write-Output "Outbound HTTP Access added to Security Group"
TypeError: Cannot read property 'pipe' of undefined
開発している途中で、sls deploy
するとタイトルの通りのエラーが表示されるようになった。
調査した結果、私の場合はserverless-layers
とserverless-s3-sync
の両方を同一ディレクトリにインストールしていたことが原因と判明。(もともとserver/serverless.yml
でクライアント側のソースも含めて一括デプロイしていました)
serverless-layers
はserver
ディレクトリ、serverless-s3-sync
はclient
ディレクトリにインストールし、それぞれのディレクトリに分割してserverless.yml
を定義たうえでdeploy.ps1
を用いて一括実行する形に見直すことで解決した。
yarn build時の環境変数読み込み
deploy.ps1
でバックエンド側のデプロイとクライアント側のソースビルド・デプロイを一括実行するようスクリプトを組む中で、タイトル部分の挙動が手動実行時と違うことを検知した。
もともとスクリプトを組む前はこちらのサイトを参考にproduction環境での環境変数をenv.production
に定義していたが、スクリプト上で.env.production
ファイルを作成&yarn build
を実行するとうまくenv.production
ファイルを読み込まないことが判明。
以下のように.env.production
ファイルを使わず、直接環境変数に設定することで解決しました。おそらく権限周りの問題なのだと思われるが、よくわからず・・・。
#deploy.ps1抜粋
$env:REACT_APP_API_URL_PROD = aws cloudformation describe-stacks --output text --stack-name feed-notify-dev --query 'Stacks[].Outputs[?OutputKey==`ServiceEndpoint`].[OutputValue]'
yarn build
Write-Output "Client App Built"
#終わりに
今回、初めてServerless Frameworkで本格的にアプリ構築をしてみましたが、いろいろとハマってしまって難しいと感じた面もありつつ、Serverless Frameworkそのものやプラグインがとても便利で、非常に勉強になりつつ楽しかったです。
ReactによるUI構築も勉強になったので、今回の経験を踏まえて、また新しいアプリ構築にチャレンジしてみたいと思います。