GAミント至上主義

Web Monomaniacal Developer.

Google Cloud FunctionsにPython3.7が追加されてる件

今日Cloud FunctionsってNodeのバージョン選べるっけと思ってみたら、Python3.7があることに気づく。

f:id:uyamazak:20180720184424p:plain

package.jsonの代わりにrequirements.txtがありpipのパッケージを指定できるっぽい。

Nodeは6系と古いままのにPythonは6月ごろに出たばっかの最新の3.7とこの扱いの差はなんなんだろう。Googleの中の人のご要望だろうか。

HTTPトリガーのサンプルコードは下記のようになっていた。
使ったことないけどflaskをでラップしてくれてる感じか。

def hello_world(request):
    """Responds to any HTTP request.
    Args:
        request (flask.Request): HTTP request object.
    Returns:
        The response text or any set of values that can be turned into a
        Response object using
        `make_response <http://flask.pocoo.org/docs/0.12/api/#flask.Flask.make_response>`.
    """
    request_json = request.get_json()
    if request.args and 'message' in request.args:
        return request.args.get('message')
    elif request_json and 'message' in request_json:
        return request_json['message']
    else:
        return f'Hello World!'

Nodeのバージョン古いなぁと悩むぐらいならPythonを使うのもありかも。

さっそく、この前作ったRSSJSONにするやつをPythonで作ってみて速さなどを確認したいと思う。

uyamazak.hatenablog.com

2018/7/23 追記

とりいそぎPython版作った。CORSはちょうどいいのがなかったのでシンプルに手書きした。
レスポンスはNode版とほぼ差がない感じなので、このまま使ってみようと思う。

import feedparser
import json
RSS_URL = "https://blog.yagish.jp/rss"
WHITELIST = ['http://192.168.2.70:2105', 'http://ml30gen9.jp:2105', 'https://rirekisho.yagish.jp']
MAX_ENTRIES_NUM = 3

def rss2json(request):
    headers = {}
    origins = [val for key, val in request.headers if key == 'Origin']
    if len(origins) > 0:
        origin = origins[0]
        for allowed_url in WHITELIST:
            if origin == allowed_url:
                headers['Access-Control-Allow-Origin'] = allowed_url
                break
    headers["Content-Type"] = "application/json; charset=utf-8"
    headers["Cache-Control"] = "public, max-age=30, s-maxage=60"
    rss = {}
    try:
        raw_rss = feedparser.parse(RSS_URL)
        rss['feed'] = raw_rss['feed']
        rss['entries'] = raw_rss['entries'][:MAX_ENTRIES_NUM]
    except Exception(e):
        return (e, 500)
    else:
        return (json.dumps(rss, indent=2, ensure_ascii=False), 
                headers)

レスポンスは下記のような感じ。一部省略

{
  "feed": {
    "title": "yagishのブログ",
    "title_detail": {
      "type": "text/plain",
      "language": null,
      "base": "https://blog.yagish.jp/rss",
      "value": "yagishのブログ"
    },
    "links": [
      {
        "rel": "alternate",
        "type": "text/html",
        "href": "https://blog.yagish.jp/"
      }
    ],
    "link": "https://blog.yagish.jp/",
    "subtitle": "ブラウザでつくれる履歴書 yagish( https://rirekisho.yagish.jp )のお知らせブログです",
    "subtitle_detail": {
      "type": "text/html",
      "language": null,
      "base": "https://blog.yagish.jp/rss",
      "value": "ブラウザでつくれる履歴書 yagish( https://rirekisho.yagish.jp )のお知らせブログです"
    },
    "updated": "Wed, 18 Jul 2018 18:36:13 +0900",
    "updated_parsed": [
      2018,
      7,
      18,
      9,
      36,
      13,
      2,
      199,
      0
    ],
    "docs": "http://blogs.law.harvard.edu/tech/rss",
    "generator_detail": {
      "name": "Hatena::Blog"
    },
    "generator": "Hatena::Blog"
  },
  "entries": [
    {
      "title": "バックアッププレビューなどアップデートのお知らせ",
      "title_detail": {
        "type": "text/plain",
        "language": null,
        "base": "https://blog.yagish.jp/rss",
        "value": "バックアッププレビューなどアップデートのお知らせ"
      },
      "links": [
        {
          "rel": "alternate",
          "type": "text/html",
          "href": "https://blog.yagish.jp/entry/2018/07/18/183613?utm_source=feed"
        },
        {
          "type": "image/png",
          "length": "0",
          "href": "https://cdn.blog.st-hatena.com/images/theme/og-image-1500.png",
          "rel": "enclosure"
        }
      ],
      "link": "https://blog.yagish.jp/entry/2018/07/18/183613?utm_source=feed",
      "summary": "<p>次回のアップデートで以下の機能を追加・変更予定です。日程は追ってご連絡します。</p>",
      "summary_detail": {
        "type": "text/html",
        "language": null,
        "base": "https://blog.yagish.jp/rss",
        "value": "<p>次回のアップデートで以下の機能を追加・変更予定です。日程は追ってご連絡します。</p>"
      },
      "published": "Wed, 18 Jul 2018 18:36:13 +0900",
      "published_parsed": [
        2018,
        7,
        18,
        9,
        36,
        13,
        2,
        199,
        0
      ],
      "id": "hatenablog://entry/10257846132602357979",
      "guidislink": false
    }
  ]
}

ブログの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



yagish履歴書がはてブ(IT)1位でうれしい

rirekisho.yagish.jp

リリース出したのは7/11だけど、なぜか今朝になっていつも見てるはてブ上位にいた
f:id:uyamazak:20180712101522p:plain

うれしい

ファビコンがルートに置いてないせいか出てないから直そう

2018/7/12 12:43追記

テクノロジー1位やったー
この記事のタイトルも変えた

f:id:uyamazak:20180712124513p:plain

なんでこんなにブクマつくんだろう