文系プログラマによるTIPSブログ

文系プログラマ脳の私が開発現場で学んだ事やプログラミングのTIPSをまとめています。

【キャッシュ】はてなブックマークウィジェットを自力で実装する【高速化】

皆大好き、はてなブックマークウィジェットについてのトピックです。
f:id:treeapps:20170818174241p:plain

ブログパーツの設定

このウィジェットがちょっと問題があるので、色々解決策を考えてみたいと思います。
f:id:treeapps:20140221011112p:plain

問題点

httpsの画面で使えない

はてなブックマークウィジェットで読み込まれるjsですが・・・

            hatenadiary: {
                wrapper:'hatena-module',
                title:  'hatena-moduletitle',
                body:   'hatena-modulebody',
                footer: 'hatena-modulefooter',
                cssfile:'http://b.hatena.ne.jp/css/widget-hatenadiary.css'
            },

(;^ω^) うわぁ・・http固定でcssを読み込んでる・・

皆さんはhttpsの画面はhttpのリソースを読み込むと警告が表示される仕様はご存知でしょうか。js、css、画像ファイルが主な警告の原因となりがちです。
google adsenseで表示される画像がhttpであっても警告が表示されてしまいます。IEの場合は画面下部にデカデカと警告バーが表示される程の鬱陶しさです。

こんな警告が出てしまってはhttpsの画面ではてなブックマークウィジェットは使えません・・・

キャッシュが効かない

js自体のキャッシュは効きます、が、jsで取得するブックマークの内容まではキャッシュできません。
内部的にはブックマーク情報をjsonで取得しています。つまり画面を表示する度にjsonを取得、という無駄な処理をしています。
物凄い勢いでブックマークされるサイトならともかく、当ブログのようにPV数が少ないサイトでは、キャッシュをして4時間に1回程度の頻度でキャッシュ破棄・更新、をすれば十分です。

非同期スクリプトではない

widget.jsはピュアjsで記述されていますが、はてなのサーバにjsonをリクエストする部分等、非同期処理は全く記述されていません。つまりウィジェットの処理中はhtmlの描画処理はブロック(停止)されます。これは大問題です。

対応策

自前でウィジェット作っちゃえ!!

まずはウィジェットのコードを見てみます。widget.jsを読み込んでますね。

<div class="hatena-widget widget-hatena-bookmark-widget">
<script language="javascript" type="text/javascript" src="http//b.hatena.ne.jp/js/widget.js" charset="utf-8"></script>
<script language="javascript" type="text/javascript">
Hatena.BookmarkWidget.url   = "http://treeapps.hatenablog.com/";
Hatena.BookmarkWidget.title = "treeのメモ帳の人気記事";
Hatena.BookmarkWidget.sort  = "count";
Hatena.BookmarkWidget.width = 145;
Hatena.BookmarkWidget.num   = 10;
Hatena.BookmarkWidget.theme = "default";
Hatena.BookmarkWidget.load();
</script>

ではwidget.jsの中身を解読してみます。

        var createRequest = function(id) {
            var url = Hatena.BookmarkWidget.url;
            var sort = Hatena.BookmarkWidget.sort;
            return function () {
                var request = 'http://b.hatena.ne.jp/entrylist/json?' + [
                    'callback=Hatena.BookmarkWidget.callbacks['+ id +']',
                    'url='  + encodeURIComponent(url),
                    'sort=' + sort
                ].join('&');
                var script = $E('script', { defer: 'defer', type: 'text/javascript', charset: 'utf-8', src: request });
                document.getElementsByTagName('head')[0].appendChild(script)
            }
        };

( ^ω^) json?これだ!!
ここではてなのサーバに対してブックマーク情報をjsonで取得するリクエストを生成しているようです。このコードを元に、jsonを取得するurlを作ってみます。すると以下のようになります。

http://b.hatena.ne.jp/entrylist/json?&url=http://treeapps.hatenablog.com/&sort=count

これを実行するとjsonが返ります。ということは・・・?

( ^ω^) 自前でウィジェットを作れる!

jsonをパースしてEHCache等でキャッシュし、自分でウィジェットのhtmlを書けばよいのです。EHCacheに関しては以前に記事を書いたので、合わせてご覧下さい。


更に、javaでhttpリクエストをし、json or xmlをDTOにバインドする簡単ライブラリの記事も書いたので、是非ご覧下さい!

後日談:実際やってみた!

今回はjavaで実装します。JSONをDTOにマッピングするために、JSONICを使うのでクラスパスに通します。
JSONIC - simple json encoder/decoder for java
guavaも使うので、合わせてクラスパスに通します。
GitHub - google/guava: Google core libraries for Java

DTO

public class HatenaBookmarkWidgetDto {

    private List<Bookmark> bookmarks;

    public static class Bookmark {

        private String link;
        private String count;
        private String title;

        public String getLink() {
            return link;
        }
        public void setLink(String link) {
            this.link = link;
        }
        public String getCount() {
            return count;
        }
        public void setCount(String count) {
            this.count = count;
        }
        public String getTitle() {
            return title;
        }
        public void setTitle(String title) {
            this.title = title;
        }
    }

    public List<Bookmark> getBookmarks() {
        return bookmarks;
    }
    public void setBookmarks(List<Bookmark> bookmarks) {
        this.bookmarks = bookmarks;
    }
}

jsonを取得してDTOにマッピングする

public List<Bookmark> getHatenaBookmarkWidget() {
    URL url = new URL("http://b.hatena.ne.jp/entrylist/json?&url=http://treeapps.hatenablog.com/&sort=count");
    BufferedReader br = null;
    try {
        br = new BufferedReader(new InputStreamReader(url.openStream(), "UTF8"));
        List<String> lines = Lists.newArrayList();
        String line;
        while ((line = br.readLine()) != null)
            lines.add(line);
        String json = Joiner.on(' ').join(lines);

        // jsonpをjsonに変換
        StringBuilder sb = new StringBuilder();
        sb.append("{ bookmarks : [");
        sb.append(json.substring(2, json.length() - 3));
        sb.append("]}");

        HatenaBookmarkWidgetDto hatenaBookmark = JSON.decode(sb.toString(), HatenaBookmarkWidgetDto.class);
        return hatenaBookmark.getBookmarks();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        Closeables.closeQuietly(br);
    }
}

取得したjsonは実はjsonpなので、jsonに無理やり変換してからDTOにバインドしてます。後はこれを画面に表示するだけです。↓tree-tipsではこんな感じで実装してみました。
f:id:treeapps:20140221114713p:plain
自由にデザインできるんですが、ユーザの慣れの観点を考慮して敢えて見た目を似せました。
実物を見たい方はこちらです↓↓↓