AMP の update-cache 要求を apikey.pub を使用して送ってみる


AMP の Cache 更新について調べていたところ、
Update AMP Content  |  Google AMP Cache  |  Google Developers の記載を見つけました。
記事を読む限り、自サイトに、apikey.pub という公開鍵を置くと、
より早くCache 更新をしてくれそうなので、試してみました。

以下、試してみた結果を記載します。


update-cache request について

Update AMP Content  |  Google AMP Cache  |  Google Developers
の冒頭の文章をGoogle 翻訳に突っ込んだ結果になります。

更新キャッシュ要求

更新キャッシュ要求は、追加のセキュリティ手段を備えているため、より高い要求速度を可能にします。
更新キャッシュ要求では、ドメイン所有者がRSAキーを使用して要求に署名し、一致する公開鍵を元のドメインの標準URLから提供する必要があります。
署名された要求をAMPキャッシュに発行することによって、現在キャッシュされているバージョンのドキュメントをフラッシュすることができます。
更新キャッシュ要求は、このアドレスで呼び出されます。

公開鍵置いたほうが、より早くCacheを更新してくれるようです。


実施したこと

ドキュメントを読む限り、以下の手順を踏めば、
設定はできそうですので順に実施していきます。

  1. update-cache 要求を送るドメインの選定

  2. RASキーの生成

  3. update-cache 要求を送る


1. update-cache 要求を送るドメインの選定

https://cdn.ampproject.org/caches.json に AMP の Cache を保持しているドメインが記載されているようです。
URL にアクセスすると、以下のJSONファイルが取得できました。

  • caches.json

    {
      "caches": [
        {
          "id": "google",
          "name": "Google AMP Cache",
          "docs": "https://developers.google.com/amp/cache/",
          "updateCacheApiDomainSuffix": "cdn.ampproject.org"
        }
      ]
    }
    
    おそらくこれが、今後増えていくのではないかと思いましたので、
    以下のような、updateCacheApiDomainSuffix に記載されているdomain を取得するpython スクリプトを作成しました。

  • get_domain_suffix.py

    # -*- coding: utf-8 -
    
    import requests
    
    CACHES_JSON_URL = "https://cdn.ampproject.org/caches.json"
    
    
    def main():
    
        print("### " +  __file__ + " START ")
    
        json_o = requests.get(CACHES_JSON_URL).json()
        for cache in json_o.get("caches"):
            print(cache.get("updateCacheApiDomainSuffix"))
    
        print("### " + __file__ + " END ")
    
    if __name__ == '__main__':
        main()
    


2. RSAキーの生成、公開

  • キーの生成
    公開鍵と、秘密鍵のキーペアを生成します。
    ドキュメント記載のコマンドをそのまま実行します。
openssl genrsa 2048 > private-key.pem
openssl rsa -in private-key.pem -pubout >public-key.pem

以下の秘密鍵と、公開鍵が生成されました。

ls 
------------------------------
private-key.pem  public-key.pem
------------------------------
PEM って拡張子は、ファイルの種類(鍵の中身)ではなくて、エンコーディングの方式を表しているそうです。
RSA鍵、証明書のファイルフォーマットについて - Qiitaが勉強になりました。

  • キーの公開
    HTTPサーバーの設定を変更して、 https://example.com/.well-known/amphtml/apikey.pub と、生成したpublic-key.pem の紐付けをし、
    外部公開します。
    このキーは、content-type "text/plain" になるように、設定します。1
    [1]. 私は、content-type "text/html" で配信してしまってました。

3. update-cache 要求を送る

RSAキーの生成が済んだら、実際に、update-cache 要求を送ります。
これがなかなか大変で、以下の手順を踏む必要があります。

  1. AMP Cache の URL のドメイン部を除去した文字列をダイジェスト値として、電子署名を生成

  2. 1.で生成した電子署名をbase64エンコードし、amp_url_signature として使う。

  3. update-cache の要求URLを生成し、リクエスト送付。

とりあえず、更新通知が送れるか試してみるのに、
以下のようなスクリプトを作成してみました。

{www-domain-com}{www.domain.com}{page_url}{your_private-key_path}
を読み替えてもらえれば、動作するかと思います。

  • update_amp_cache.py

    # -*- coding: utf-8 -                                                                                                                                                             
    
    from OpenSSL import crypto
    from OpenSSL.crypto import FILETYPE_PEM, load_privatekey
    import requests
    import datetime
    import time
    import base64
    
    AMP_BASE_URL = "https://{www-domain-com}.cdn.ampproject.org";
    SIGNATURE_URL = "/update-cache/c/s/{www.domain.com}/{page_url}?amp_action=flush&amp_ts="
    
    def main():
    
        try 
            key = open("{your_private-key_path}")
    
            # UNIXタイムスタンプを取得
            ts = time.mktime(datetime.datetime.now().utctimetuple())
            ts = int(ts)
    
            # 1. AMP Cache の URL のドメイン部を除去した文字列をダイジェスト値として、電子署名を生成 
            url = SIGNATURE_URL + str(ts)
            p_key = load_privatekey(FILETYPE_PEM, key.read()) 
            sign = crypto.sign(p_key, url ,"sha256")
    
            # 2. `1.`で生成した電子署名をbase64エンコードし、`amp_url_signature` として使う。  
            enc_sign = base64.urlsafe_b64encode(sign)    
    
            # 3. update-cache の要求URLを生成し、リクエスト送付
            amp_url = AMP_BASE_URL + url + "&amp_url_signature=" + enc_sign
    
            print(amp_url)
            r = requests.get(amp_url)
            print(r.text)
    
        finally:
            key.close()
    if __name__ == '__main__':
        main()
    

  • 補足 ライブラリのインストール
    スクリプトの実行には、requests と、pyopenssl
    のインストールが必要になります。

    pip install requests
    pip install pyopenssl
    

  • 実行結果
    上記スクリプトを実行すると、うまくいくと以下出力されます。
    レスポンスのコンテンツBody は、OK になるようです。

    https://{www-domain-com}.cdn.ampproject.org/update-cache/c/s/{www.domain.com}/{page_url}?amp_action=flush&amp_ts=1501070789&amp_url_signature=leYSwvcTxiT...
    OK
    

ちなみに、amp_ts=1501070789 のUnix タイムスタンプの指定を忘れて実行すると、以下404エラーが返ります。2
[2] r.textだからか、途中でHTMLが千切れてます。

  • 失敗時のr.text の中身

    <!DOCTYPE html>
    <html lang=en>
      <meta charset=utf-8>
      <meta name=viewport content="initial-scale=1, minimum-scale=1, width=device-width">
      <title>Error 404 (Not Found)!!1</title>
      <style>
        *{margin:0;padding:0}html,code{font:15px/22px arial,sans-serif}html{background:#fff;color:#222;padding:15px}body{margin:7% auto 0;max-width:390px;min-height:180px;padding:30px 0 15px}* > body{background:url(//www.google.com/images/errors/robot.png) 100% 5px no-repeat;padding-right:205px}p{margin:11px 0 22px;overflow:hidden}ins{color:#777;text-decoration:none}a img{border:0}@media screen and (max-width:772px){body{background:none;margin-top:0;max-width:none;padding-right:0}}#logo{background:url(//www.google.com/images/branding/googlelogo/1x/googlelogo_color_150x54dp.png) no-repeat;margin-left:-5px}@media only screen and (min-resolution:192dpi){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat 0% 0%/100% 100%;-moz-border-image:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) 0}}@media only screen and (-webkit-min-device-pixel-ratio:2){#logo{background:url(//www.google.com/images/branding/googlelogo/2x/googlelogo_color_150x54dp.png) no-repeat;-webkit-background-size:100% 100%}}#logo{display:inline-block;height:54px;width:150px}
      </style>
      <a href=//www.google.com/><span id=logo aria-label=Google></span></a>
      <p><b>404.</b> <ins>That’s an error.</ins>
      <p>The requested URL <code>/update-cache/c/s/{www.domain.com}/{page_url}?amp_action=flush&amp;amp_ts=&amp;amp_url_signature=pKqqgI....</code> was not found on this server.  <ins>That’s all we know.</ins>
    

  • 参考にしたもの
    php - Using /update-cache requests to update AMP pages - Stack Overflow に、
    php で、update-cache URLを生成する gist にリンクがあり、その実装が参考になりました。


4. 更新対象の URL を外部から指定可能にする

後日、3. のpython を実行する記載があり、
URL指定して更新できないのが不便だったので、以下のように、実装しなおしました。
引数の処理にclick を使うようにしたので、別途インストールが必要です。

AMP_BASE_URL = "https://{www-domain-com}.cdn.ampproject.org";{www-domain-com} と、
秘密鍵のpath は変更する前提ですが、更新対象 URL を外部から指定できるようにしました。

  • update_amp_cache.py

    # -*- coding: utf-8 -                                                                                                                                                               
    from OpenSSL import crypto
    from OpenSSL.crypto import FILETYPE_PEM, load_privatekey
    import requests
    import datetime
    import time
    import base64
    import click
    from urlparse import urlparse
    
    AMP_BASE_URL = "https://www-monotalk-xyz.cdn.ampproject.org";
    
    @click.command()
    @click.option('-url', help='update-cache target url', required=1)
    def main(url):
    
        try:
            key = open("{your_private-key_path}")
    
            # UNIXタイムスタンプを取得
            ts = time.mktime(datetime.datetime.now().utctimetuple())
            ts = int(ts)
    
            # 1. AMP Cache の URL のドメイン部を除去した文字列をダイジェスト値として、電子署名を生成 
            parsed_url = urlparse(url)
            signature_url = "/update-cache/c/"
            if "https" == parsed_url.scheme:
                signature_url = signature_url + "s/"
            signature_url = signature_url + parsed_url.netloc + parsed_url.path + "?amp_action=flush&amp_ts="
            url = signature_url + str(ts)
            # 秘密鍵の読み出し、電子署名の作成
            p_key = load_privatekey(FILETYPE_PEM, key.read()) 
            sign = crypto.sign(p_key, url ,"sha256")
    
            # 2. `1.`で生成した電子署名をbase64エンコードし、`amp_url_signature` として使う。  
            enc_sign = base64.urlsafe_b64encode(sign)    
    
            # 3. update-cache の要求URLを生成し、リクエスト送付
            amp_url = AMP_BASE_URL + url + "&amp_url_signature=" + enc_sign
    
            print("####################")
            print("REQUEST URL:" + amp_url)
            print("####################")
    
            r = requests.get(amp_url)
    
            print("####################")
            print("RESULT:" + r.text)
            print("####################")
    
        finally:
            key.close()
    
    if __name__ == '__main__':
        main() 
    

  • 参考にしたもの
    urlparse – URL を部分文字列に分割する - Python Module of the Week
    urlparse を使用して、URLを分割する。
    Python: コマンドラインパーサの Click が便利すぎた - CUBE SUGAR CONTAINER

  • 実行する
    以下コマンドで、実行可能です。

    python2.7 update_amp_cache.py -url https://www.monotalk.xyz/
    


感想

公開鍵と、秘密鍵の発行とか、中々敷居高いですが、
なんとかupdate-cache を実行することができましたが、
スクリプトが汚かったり、1件実行しかできなかったりします。
amp の validation api や、amp url api と組み合わせて、
うまくbatch 処理化して、スケジュール実行できるといい感じになりそうなので、
そのうち試みてみようかと思います。

以上です。

コメント