GAミント至上主義

Web Monomaniacal Developer.

ブログのRSSをCloud Functionsを使ってJSONに変換してajaxで使う

yagish履歴書で今後のアップデートのお知らせ用にはてなブログProを利用しようと考えている。

rirekisho.yagish.jp
(関係ないけどはてブ800超えてうれしい)

もちろんRSSがあるので、これをVue.jsアプリ内で読み込んでもいい。
でも、RSSとかXMLとかを扱うとなると、クライアント側にそれだけのためにライブラリがいくつも必要になる。
せっかくLighthouse対策でjsを減らすのがんばったのに嫌。

uyamazak.hatenablog.com

そこでCloud Functionsを使って、jsonに変換して返せないか考えた。
そうすれば、もともと使っているaxiosだけで済むし、コードも減るし、使いやすい。

まず、RSSJSONに変換するのはそのまんまのライブラリがあった。
すごいシンプルで言うことない。

www.npmjs.com

次にajaxで読み込むのでCORS対策。
Firebase HostingでCloud Functionsにリライトして接続すれば同一ドメインになるのでいらない。

Cloud Functions による動的コンテンツの配信  |  Firebase

でも今回はローカルでもテストしやすいのでCORSで許可することにした。

検索したらまさにのstackoverflowがあった。
stackoverflow.com

複数ドメインを動的に処理するのはcorsのドキュメントにあったのを使う。
github.com

これを組み合わせたのが下記。
これをCloud Functionsに作ってHTTPトリガーでデプロイする。
RSSの量によるかもだけどメモリは128MBでも良さげ。

Cloud FunctionsはCDNを通してるのでCache-Controlをつけておけば爆速で返せるので成功時だけつけておく。
期限は必要に応じてもっと短くてもいいかも。

res.set('Cache-Control', 'public, max-age=300, s-maxage=600');

index.js

const Feed = require('rss-to-json');
const RssURL = 'https://blog.yagish.jp/rss';
const whitelist = ['http://192.168.0.1', 'https://rirekisho.yagish.jp/']
const corsOptions = {
  origin: function (origin, callback) {
    if (whitelist.indexOf(origin) !== -1) {
      callback(null, true)
    } else {
      callback(new Error('Not allowed by CORS'))
    }
  }
}
const cors = require('cors')(corsOptions)

/**
 * Responds to any HTTP request.
 *
 * @param {!Object} req HTTP request context.
 * @param {!Object} res HTTP response context.
 */
exports.RssToJson = (req, res) => {
  Feed.load(RssURL, function(err, rss){
    cors(req, res, () => {});
    if(err){
      res.status(503).send(err);
    }else{
      res.set('Cache-Control', 'public, max-age=300, s-maxage=600');
      res.status(200).send(rss);
    }
  });
};

package.json

{
  "name": "sample-http",
  "version": "0.0.1",
  "dependencies": {
   "rss-to-json": "^1.0.4",
   "cors": "^2.8.4"
 }
}

whitelistを変えてみてエラーになったり、ならなかったりを確認した。

元のRSS
https://blog.yagish.jp/rss

<?xml version="1.0"?>
<rss version="2.0">
  <channel>
    <title>yagishのブログ</title>
    <link>https://blog.yagish.jp/</link>
    <description></description>
    <lastBuildDate>Wed, 18 Jul 2018 18:36:13 +0900</lastBuildDate>
    <docs>http://blogs.law.harvard.edu/tech/rss</docs>
    <generator>Hatena::Blog</generator>
    
      
      
        <item>
          <title>バックアップ機能アップデートのお知らせ</title>
          <link>https://blog.yagish.jp/entry/2018/07/18/183613?utm_source=feed</link>          <description>&lt;p&gt;バックアップ機能アップデートのお知らせ&lt;/p&gt;
</description>          <pubDate>Wed, 18 Jul 2018 18:36:13 +0900</pubDate>
          <guid isPermalink="false">hatenablog://entry/10257846132602357979</guid>
          
          <enclosure url="https://cdn.blog.st-hatena.com/images/theme/og-image-1500.png" type="image/png" length="0" />
        </item>
      
    
  </channel>
</rss>

こんなのが返ってきた。まだ記事数も文章もないので速度は速い。

{
    "items": [
        {
            "title": "バックアップ機能アップデートのお知らせ",
            "description": "<p>バックアップ機能アップデートのお知らせ</p>",
            "link": "https://blog.yagish.jp/entry/2018/07/18/183613?utm_source=feed",
            "url": "https://blog.yagish.jp/entry/2018/07/18/183613?utm_source=feed",
            "created": 1531906573000,
            "enclosures": [
                {
                    "url": "https://cdn.blog.st-hatena.com/images/theme/og-image-1500.png",
                    "type": "image/png",
                    "length": "0"
                }
            ]
        }
    ],
    "title": "yagishのブログ",
    "description": "",
    "url": "https://blog.yagish.jp/",
    "image": ""
}

pubDateはタイムスタンプに変換されcreatedという名前になってる。

こういうクライアント側でもできなくはないけど、サーバーでやった方がユーザー&クライアント側に優しいものにCloud Functionsは最適なので今後も使い所を探していこうと思う。

2018/07/23追記

Python3.7が使えるようになったので作ってみた
uyamazak.hatenablog.com