6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Serverless Frameworkでニュースサイトの新着記事をLINEで通知するアプリを作る

Last updated at Posted at 2021-12-05

アプリケーション構築を学習する一環として、RSSフィードで配信された記事をLINE Messaging API経由で通知するアプリをServerless Frameworkで作りました。

友達追加すると、
follow

新着記事を定期配信したり、手動で取得したりすることができるアプリです。
subscribe.png

成果物はGithubにアップロードしていますが、せっかくなのでアプリの概要と、ハマったポイントをこちらにも投稿しておこうと思います。(以下、Github上のREADME.mdのほぼ抜粋です)

概要

  • RSSFeedで配信された記事を、LINE Messaging API経由で購読する
  • 購読対象の記事は、設定用サイトで追加・削除・有効化・無効化が可能
  • 定期購読のほかに、LINEでメッセージを送ることで記事を手動購読することも可能
  • システム構成は以下の通り。AWS上にソースをデプロイし、LINE Messaging API・LIFFと連携させる。

FeedNotify_Configuration.drawio.png

環境

種類 バージョン
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のプラグインは以下の通り。

server/serverless.yml抜粋
plugins:
  - serverless-dynamodb-local #DynamoDB接続部分の開発・テスト用
  - serverless-vpc-plugin #Lambda用のVPC作成
  - serverless-offline #ローカルでの開発・テスト
  - serverless-layers  #Lambda Layersを利用するためのプラグイン
client/serverless.yml抜粋
plugins:
  - serverless-s3-sync #ビルドしたフロントエンドのS3アップロード
  - serverless-cloudfront-invalidate #CloudFrontのキャッシュ削除自動化

※2 DynamoDBレコードの構成は以下の通り。

  • userId:ユーザID
  • lastSubscribe:RSSフィードの最終配信日時
  • subscribeFeeds:購読するRSSフィード情報(ここではPublicKeyとDevelopersIOの2つ)
  • name:ユーザ名
DynamoDBのレコード
{
 "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.localHTTPS = "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-layersserverless-s3-syncの両方を同一ディレクトリにインストールしていたことが原因と判明。(もともとserver/serverless.ymlでクライアント側のソースも含めて一括デプロイしていました)

serverless-layersserverディレクトリ、serverless-s3-syncclientディレクトリにインストールし、それぞれのディレクトリに分割して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構築も勉強になったので、今回の経験を踏まえて、また新しいアプリ構築にチャレンジしてみたいと思います。

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?