11月 25

Nginxを使ったWordPressのチューニングといえば、フロントエンドのNginxとバックエンドのNginx(もしくはApache)に分けてproxy cacheを効かせるのが王道です。
さらにWP Super Cacheプラグインを利用してなるべくPHPやMySQLにアクセスさせないようにすると、手軽で絶大なパフォーマンスアップが可能です。

今回はそこからもう一歩進めたチューニングについて書きたいと思います。

二段階層を廃したシンプルな構成

まずは、図をご覧ください。

構成図

前述の王道チューニングの構成はA図となります。

proxy cacheはNginxがバックエンドのサーバーに処理を回し、返ってきたレスポンスをキャッシュして、Nginx自身がキャッシュを返すことでパフォーマンスを上げる仕組みです。
A図-1がキャッシュの無いアクセス、A図-2がキャッシュが効いているアクセスを表しています。
A図-3は静的なファイル(css, js, jpgなど)をフロントのNginxが直接返すことを表しています。

fastcgi cache(HttpFcgiModule)

proxy cache自体はバックエンドのサーバーがHTTPでコンテンツを返してくれれば、どんなアプリケーションでも問わない汎用的なキャッシュ機能です。つまり、proxy cacheを利用しようとするともうひとつHTTPサーバーが必要になり、A図のようにHTTPのレイヤーが二段構成になります。

Nginxにはproxy cacheと似た機能でfastcgi cacheという機能(HttpFcgiModule)があります。これはバックエンドのFastCGIのレスポンスをキャッシュする機能です。
これを利用すると、proxy cacheを利用したのと同じ効果をHTTPサーバーなしに実現できます。
A図の8080ポートのサーバーが必要なくなり、B図の構成になります。
無駄な通信と処理がなくなり、シンプルです。

WP Super Cacheを最大限利用する

WP Super Cacheは、アクセスごとに動的に生成される記事を、静的なHTMLファイルとして保存(キャッシュ)して、それを返すように振舞うプラグインです。
キャッシュが効いている間はMySQLにアクセスすることが無いのでパフォーマンスが上がります。

.htacessの落とし穴

WP Super Cacheを利用すると、.htaccessに以下の設定が追加されます。
(利用しているプラグインによって多少変わります)

# BEGIN WPSuperCache

RewriteEngine On
RewriteBase /
AddDefaultCharset UTF-8
RewriteCond %{REQUEST_METHOD} !POST
RewriteCond %{QUERY_STRING} !.*=.*
RewriteCond %{HTTP:Cookie} !^.*(comment_author_|wordpress_logged_in|wp-postpass_).*$
RewriteCond %{HTTP:X-Wap-Profile} !^[a-z0-9\"]+ [NC]
RewriteCond %{HTTP:Profile} !^[a-z0-9\"]+ [NC]
RewriteCond %{HTTP_USER_AGENT} !^.*(2.0\ MMP|240x320|400X240|AvantGo|BlackBerry|Blazer|Cellphone|Danger|DoCoMo|Elaine/3.0|EudoraWeb|Googlebot-Mobile|hiptop|IEMobile|KYOCERA/WX310K|LG/U990|MIDP-2.|MMEF20|MOT-V|NetFront|Newt|Nintendo\ Wii|Nitro|Nokia|Opera\ Mini|Palm|PlayStation\ Portable|portalmmm|Proxinet|ProxiNet|SHARP-TQ-GX10|SHG-i900|Small|SonyEricsson|Symbian\ OS|SymbianOS|TS21i-10|UP.Browser|UP.Link|webOS|Windows\ CE|WinWAP|YahooSeeker/M1A1-R2D2|iPhone|iPod|Android|BlackBerry9530|LG-TU915\ Obigo|LGE\ VX|webOS|Nokia5800).* [NC]
RewriteCond %{HTTP_user_agent} !^(w3c\ |w3c-|acs-|alav|alca|amoi|audi|avan|benq|bird|blac|blaz|brew|cell|cldc|cmd-|dang|doco|eric|hipt|htc_|inno|ipaq|ipod|jigs|kddi|keji|leno|lg-c|lg-d|lg-g|lge-|lg/u|maui|maxo|midp|mits|mmef|mobi|mot-|moto|mwbp|nec-|newt|noki|palm|pana|pant|phil|play|port|prox|qwap|sage|sams|sany|sch-|sec-|send|seri|sgh-|shar|sie-|siem|smal|smar|sony|sph-|symb|t-mo|teli|tim-|tosh|tsm-|upg1|upsi|vk-v|voda|wap-|wapa|wapi|wapp|wapr|webc|winw|winw|xda\ |xda-).* [NC]
RewriteCond %{HTTP_USER_AGENT} !^(DoCoMo/|J-PHONE/|J-EMULATOR/|Vodafone/|MOT(EMULATOR)?-|SoftBank/|[VS]emulator/|KDDI-|UP\.Browser/|emobile/|Huawei/|IAC/|Nokia|mixi-mobile-converter/)
RewriteCond %{HTTP_USER_AGENT} !(DDIPOCKET;|WILLCOM;|Opera\ Mini|Opera\ Mobi|PalmOS|Windows\ CE;|PDA;\ SL-|PlayStation\ Portable;|SONY/COM|Nitro|Nintendo)
RewriteCond %{HTTP:Accept-Encoding} gzip
RewriteCond %{DOCUMENT_ROOT}/wp-content/cache/supercache/%{HTTP_HOST}/$1/index.html.gz -f
RewriteRule ^(.*) "/wp-content/cache/supercache/%{HTTP_HOST}/$1/index.html.gz" [L]

RewriteCond %{REQUEST_METHOD} !POST
RewriteCond %{QUERY_STRING} !.*=.*
RewriteCond %{HTTP:Cookie} !^.*(comment_author_|wordpress_logged_in|wp-postpass_).*$
RewriteCond %{HTTP:X-Wap-Profile} !^[a-z0-9\"]+ [NC]
RewriteCond %{HTTP:Profile} !^[a-z0-9\"]+ [NC]
RewriteCond %{HTTP_USER_AGENT} !^.*(2.0\ MMP|240x320|400X240|AvantGo|BlackBerry|Blazer|Cellphone|Danger|DoCoMo|Elaine/3.0|EudoraWeb|Googlebot-Mobile|hiptop|IEMobile|KYOCERA/WX310K|LG/U990|MIDP-2.|MMEF20|MOT-V|NetFront|Newt|Nintendo\ Wii|Nitro|Nokia|Opera\ Mini|Palm|PlayStation\ Portable|portalmmm|Proxinet|ProxiNet|SHARP-TQ-GX10|SHG-i900|Small|SonyEricsson|Symbian\ OS|SymbianOS|TS21i-10|UP.Browser|UP.Link|webOS|Windows\ CE|WinWAP|YahooSeeker/M1A1-R2D2|iPhone|iPod|Android|BlackBerry9530|LG-TU915\ Obigo|LGE\ VX|webOS|Nokia5800).* [NC]
RewriteCond %{HTTP_user_agent} !^(w3c\ |w3c-|acs-|alav|alca|amoi|audi|avan|benq|bird|blac|blaz|brew|cell|cldc|cmd-|dang|doco|eric|hipt|htc_|inno|ipaq|ipod|jigs|kddi|keji|leno|lg-c|lg-d|lg-g|lge-|lg/u|maui|maxo|midp|mits|mmef|mobi|mot-|moto|mwbp|nec-|newt|noki|palm|pana|pant|phil|play|port|prox|qwap|sage|sams|sany|sch-|sec-|send|seri|sgh-|shar|sie-|siem|smal|smar|sony|sph-|symb|t-mo|teli|tim-|tosh|tsm-|upg1|upsi|vk-v|voda|wap-|wapa|wapi|wapp|wapr|webc|winw|winw|xda\ |xda-).* [NC]
RewriteCond %{HTTP_USER_AGENT} !^(DoCoMo/|J-PHONE/|J-EMULATOR/|Vodafone/|MOT(EMULATOR)?-|SoftBank/|[VS]emulator/|KDDI-|UP\.Browser/|emobile/|Huawei/|IAC/|Nokia|mixi-mobile-converter/)
RewriteCond %{HTTP_USER_AGENT} !(DDIPOCKET;|WILLCOM;|Opera\ Mini|Opera\ Mobi|PalmOS|Windows\ CE;|PDA;\ SL-|PlayStation\ Portable;|SONY/COM|Nitro|Nintendo)
RewriteCond %{DOCUMENT_ROOT}/wp-content/cache/supercache/%{HTTP_HOST}/$1/index.html -f
RewriteRule ^(.*) "/wp-content/cache/supercache/%{HTTP_HOST}/$1/index.html" [L]


# END WPSuperCache

これは、すでにWP Super Cacheによって静的なファイルが生成されている場合、PHPを介さずにApacheが直接リクエストを返すようにする設定です。
WP Super Cacheの推奨設定では、index.htmlとそれを圧縮したindex.html.gzが用意されるので、冗長な設定になっています。

Nginxにとって.htaccessはただのテキストファイルですから、上記RewriteRuleをNginxの設定ファイルに移植しないと、WP Super Cacheは本来のパフォーマンスを発揮しません。
この移植をすることで、B図-4のように、fastcgi cacheに到達することもなく、静的ファイルと同じ処理、パフォーマンスになります。

HttpGzipStaticModule

NginxのHttpGzipStaticModuleは、同じディレクトリ内にあらかじめ圧縮ファイル(.gz)が用意されている場合に、クライアント判定を行なって自動的に圧縮ファイルを返してくれるモジュールです。(正確には、Gzip圧縮に対応しているクライアントからリクエストが来て、同ディレクトリに同タイムスタンプで同名+.gzという名前のファイルがある場合)

このモジュールを導入するメリットは、gzファイル分の.htaccessを移植する手間がなくなることです。ただし、コアモジュールではないので、コンパイル時にオプションで指定する必要があります。

./configure --with-http_gzip_static_module

コンパイルし直したnginxの入れ替えを行う場合は、こちらを参考にしてください。

NginxにはコアモジュールでHttpGzipModuleがあり、設定をオンにすることで特定のContent-TypeにGzip圧縮をかけてレスポンスを返すことができます。こちらはリクエストごとにオンザフライで圧縮をかけるので、余分に処理がかかります。あらかじめcssやjsなどの圧縮ファイルが用意できる場合は、同階層に設置してHttpGzipStaticModuleの恩恵を受けたほうがいいと思います。

サンプル設定ファイル

前述の項目を網羅して、かつ可能な限り正常に動いている環境の設定ファイルを載せています。

nginx.conf

user  nginx nginx;
worker_processes  2; # サーバーのコア数に合わせて

pid  /var/run/nginx.pid;

events {
    worker_connections  1024;
    use epoll; # Linuxの場合
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    # 最後の$request_timeは処理時間(ms)
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for" $request_time';

    sendfile        on;
    tcp_nopush     on;
    tcp_nodelay on;

    keepalive_timeout  10;

    connection_pool_size 256;
    client_header_buffer_size 1k;
    large_client_header_buffers 4 2k;
    request_pool_size 4k;
    if_modified_since before;
    ignore_invalid_headers on;
    server_tokens off;

    gzip  on;
    gzip_min_length 0;
    gzip_buffers 4 8k;
    gzip_types text/plain text/xml application/x-javascript text/css;
    gzip_disable "msie6";
    gzip_vary on;
    # HttpStaticGzipModuleをオンに
    gzip_static on;

    output_buffers 1 32k;
    postpone_output 1460;

    # fastcgi cacheの設定(httpディレクティブ内のみ有効)
    fastcgi_cache_path      /usr/local/nginx/cache levels=1:2 keys_zone=wpcache:10m max_size=50M inactive=30m;

    server {
        listen       80;
        server_name  localhost;
        charset utf-8;

        location / {
            return 403;
        }

        location /nginx_status {
            stub_status on;
            access_log off;
            allow 127.0.0.1;
            deny all;
        }  

        # redirect server error pages to the static page /50x.html
        #
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }

    include ./wordpress.conf;
}

wordpress.conf

# 設定を変えやすいようにupstreamにまとめておく
upstream phpfpm {
    # ローカルの場合はUNIXソケットで
    server unix:/var/run/php-fpm/www.sock;
}

server {
    listen       80;
    server_name  example.com;
    root /var/www/wordpress;
    access_log logs/access.log main;

    location / {
        # 静的なファイルの場合は処理をとめる
        # リクエストの度にファイルの存在をチェックするのは無駄だという意見もありますが、
        # どのURLの時index.phpに渡すのか、プラグインを含めて全仕様が分からないため、
        # これが最も安全だと思います。
        if (-f $request_filename) {
            break;
        }

        # ここからWP Super Cacheの設定(少しfastcgi cacheの設定も)
        # モバイルからのアクセスはキャッシュさせないようにする変数
        set $nocache "";
        set $supercache_file $document_root/wp-content/cache/supercache/${http_host}${uri}/index.html;
        set $supercache_uri "";
        if (-f $supercache_file) {
            set $supercache_uri /wp-content/cache/supercache/${http_host}${uri}/index.html;
        }

        if ($request_method = "POST") {
            set $supercache_uri "";
            set $nocache "1";
        }

        if ($query_string ~ .*=.*) {
            set $supercache_uri "";
        }

        if ($http_cookie ~ ^.*(comment_author_|wordpress_logged_in|wp-postpass_).*$) {
            set $supercache_uri "";
            set $nocache "1";
        }

        if ($http_x_wap_profile ~ ^[a-z0-9\"]+) {
            set $supercache_uri "";
            set $nocache "1";
        }

        if ($http_profile ~ ^[a-z0-9\"]+) {
            set $supercache_uri "";
            set $nocache "1";
        }

        if ($http_user_agent ~ ^.*(2.0\ MMP|240x320|400X240|AvantGo|BlackBerry|Blazer|Cellphone|Danger|DoCoMo|Elaine/3.0|EudoraWeb|Googlebot-Mobile|hiptop|IEMobile|KYOCERA/WX310K|LG/U990|MIDP-2.|MMEF20|MOT-V|NetFront|Newt|Nintendo\ Wii|Nitro|Nokia|Opera\ Mini|Palm|PlayStation\ Portable|portalmmm|Proxinet|ProxiNet|SHARP-TQ-GX10|SHG-i900|Small|SonyEricsson|Symbian\ OS|SymbianOS|TS21i-10|UP.Browser|UP.Link|webOS|Windows\ CE|WinWAP|YahooSeeker/M1A1-R2D2|iPhone|iPod|Android|BlackBerry9530|LG-TU915\ Obigo|LGE\ VX|webOS|Nokia5800).*) {
            set $supercache_uri "";
            set $nocache "1";
        }

        if ($http_user_agent ~ ^(w3c\ |w3c-|acs-|alav|alca|amoi|audi|avan|benq|bird|blac|blaz|brew|cell|cldc|cmd-|dang|doco|eric|hipt|htc_|inno|ipaq|ipod|jigs|kddi|keji|leno|lg-c|lg-d|lg-g|lge-|lg/u|maui|maxo|midp|mits|mmef|mobi|mot-|moto|mwbp|nec-|newt|noki|palm|pana|pant|phil|play|port|prox|qwap|sage|sams|sany|sch-|sec-|send|seri|sgh-|shar|sie-|siem|smal|smar|sony|sph-|symb|t-mo|teli|tim-|tosh|tsm-|upg1|upsi|vk-v|voda|wap-|wapa|wapi|wapp|wapr|webc|winw|winw|xda\ |xda-).*) {
            set $supercache_uri "";        
            set $nocache "1";
        }

        if ($http_user_agent ~ ^(DoCoMo/|J-PHONE/|J-EMULATOR/|Vodafone/|MOT(EMULATOR)?-|SoftBank/|[VS]emulator/|KDDI-|UP\.Browser/|emobile/|Huawei/|IAC/|Nokia|mixi-mobile-converter/)) {
            set $supercache_uri "";
            set $nocache "1";
        }

        if ($http_user_agent ~ (DDIPOCKET\;|WILLCOM\;|Opera\ Mini|Opera\ Mobi|PalmOS|Windows\ CE\;|PDA\;\ SL-|PlayStation\ Portable\;|SONY/COM|Nitro|Nintendo)) {
            set $supercache_uri "";
            set $nocache "1";
        }

        if ($supercache_uri) {
            rewrite ^ $supercache_uri last;
            break;
        }

        rewrite ^ /index.php last;
    }

    location ~ \.php {
        # 存在しないPHPファイルをシャットアウト
        if (!-f $request_filename) {
            return 404;
            break;
        }

        # fastcgi とfastcgi cacheの設定
        include ./fastcgi.conf;
        fastcgi_pass          phpfpm;
        fastcgi_cache         wpcache;
        fastcgi_cache_key     "$scheme://$host$request_uri";
        fastcgi_cache_valid   200 10m;
        fastcgi_cache_valid   404 1m;
        # $nocache = "1"の時、fastcgi cacheが無効になる
        fastcgi_cache_bypass  $nocache;
    }

    # よくアクセスされる静的ファイルにブラウザキャッシュが効くように設定
    location ~ \.(jpg|png|gif|swf|jpeg)$ {
        log_not_found off; # 404の時にerror_logに書き込まないようにする設定
        access_log off;
        expires 3d;
    }

    location ~ \.ico$ {
        log_not_found off;
        access_log off;
        expires max;
    }

    location ~ \.(css|js)$ {
        charset  UTF-8;
        access_log off;
        expires 1d;
    }

    # ドット始まりのファイルはアクセスできないように
    location ~ /\. {
        deny all;
        log_not_found off;
        access_log off;
    }

    # リライトされたWP Super Cacheのファイル
    location ~ /wp-content/cache/supercache/${http_host}${uri}/index\.html(\.gz)?$ {
        charset  UTF-8;
        internal; # この指定をしておくとURLを指定して直接アクセスできなくなる
    }

    location ~ /wp-admin/$ {
        rewrite ^/wp-admin/$ /wp-admin/index.php last;
    }
}

処理の解説

Nginxのlocationディレクティブの評価順番は、wordpress.confを例にとると、すべてのアクセスが最初に “location /”ディレクティブを通ります。(記述順番は関係ありません。)

静的ファイルのチェック

このディレクティブの先頭でリクエストされた静的ファイルが存在するかのチェックを行なっています。
静的ファイルとして存在した場合、breakで”location /”ディレクティブの処理が終わります。次に、マッチするlocationディレクティブがあればそちらが実行されます。
例えば、cssファイルだった場合は、”location ~ \.(css|js)$”ディレクティブへ処理が続いていきます。これがB図-3の流れです。

WP Super Cacheのチェック、fastcti cacheのチェック

静的ファイルとして存在しなかった場合、処理は下へ進みます。
次の処理ではWP Super Cacheが生成したキャッシュファイルの存在をチェックし、キャッシュファイルを利用するかどうかを下に続く条件で確認していきます。各条件では同時にfastcgi cacheを利用するかどうかのチェックも行なっています。

ここでは、$supercache_uriが””(空文字)に書き換えられると、WP Super Cacheの静的キャッシュファイルが使われず(下の判定で利用されます)、$nocacheが”1″に書き換えられると、fastcgi cacheが使われません( ~ \.phpディレクティブで利用されます)。

$supercache_fileが存在し、$supercache_uriが空でない場合は、”location ~ /wp-content/cache/supercache/${http_host}${uri}/index\.html(\.gz)?$”ディレクティブへ処理が続きます。これがB図-4の流れです。

全てを受け止めるindex.php

静的ファイルのチェック、WP Super Cacheのチェックが終わっても処理が続く場合は、すべてにリクエストがindex.phpへと渡され、”location ~ \.php”ディレクティブへ処理が続きます。
すでにキャッシュされていて期限が有効な場合は、fastcgi cacheが使われ(B図-2)、キャッシュがなかったり、期限が切れていた場合は、FastCGI(PHP)へ処理が渡されます(B図-1)

今回はステータスコード200の時は10分、404の時は1分キャッシュするように設定してあります。

404がキャッシュされない

目に見える記事部分のキャッシュが効いて応答が良くなると忘れてしまいがちですが、WordPressが返す404 Not Foundは無視できないくらい重い処理です。
404に対してキャッシュが作られないと、存在しない記事にアクセスがくる度にPHPとMySQLが仕事をしてしまいます。

そこで、fastcgi cacheの設定で “fastcgi_cache_valid 404 1m;” を指定しているわけですが、そのままだとキャッシュが効ききません(WordPress 3.2.1)。
調べてみると、WordPressは404の時、ヘッダーに “Cache-Control:no-cache, must-revalidate, max-age=0” を付けてレスポンスを返すことがわかりました。
このヘッダーがあるのでfastcgi cacheはキャッシュを作らないようなのです。

もっとスマートな方法があると思いますが、今回はWordPressのソースを少しいじって対応することにしました。

wp-includes/class-wp.php

function handle_404() {
    global $wp_query;

    if ( !is_admin() && ( 0 == count( $wp_query->posts ) ) && !is_404() && !is_robots() && !is_search() && !is_home() ) {
        // Don't 404 for these queries if they matched an object.
        if ( ( is_tag() || is_category() || is_tax() || is_author() || is_post_type_archive() ) && $wp_query->get_queried_object() && !is_paged() ) {
            if ( !is_404() )
                status_header( 200 );
            return;
        }
        $wp_query->set_404();
        status_header( 404 );
        //nocache_headers(); # ここをコメントアウト
    } elseif ( !is_404() ) {
        status_header( 200 );
    }
}

これで404もキャッシュされるようになりました。

おわりに

WordCamp Tokyo 2011弊社はWordPessのイベント、WordCamp 2011 Tokyoに微力ながらスポンサーとして協力させて頂きました。

ブログにもWordPressの記事が少なく、弊社がWordPressにコミットしている感じが伝わっていないと思ったので、開催前に何とか間に合って良かったです。

参考サイト

Tagged with:
9月 14

ブログの更新をTwitterへ通知するのにFriendFeedを利用しているのですが、FriendFeedのクローラ―がフィードを取得しにくるまでTwitterへは通知されず、ひどい時は1日経っても通知されないことがありました。

これを解消するために、最近Googleによって開発・公開された新しいプロトコルPubSubHubbubに対応させました。

PubSubHubbubとは

PubSubHubbubは、今までの配信者(Publishers。以下、ブログ)と購読者(Subscribers。以下、RSSリーダー)が直接やり取りをしていたその間に、ハブ(Hubs)と呼ばれる中間サーバーを配置して、そのハブを介してメッセージのやり取りをWeb Hooksの仕組みで行うものです。
Web Hooksはある決められたアクション(たとえば、「ブログの公開ボタンを押す」など)をきっかけに動作して、決められたデータを決められたURLに決められた方法で送信する仕組みです。

いままで「更新されました?」「いや、まだです。」と304の繰り返しだったフィードの世界に、「更新したしました」きっかけで、購読者に一斉に通知が行くわけですから、こんなエコな仕組みはないと思います。

WordPressで対応するには

簡単です。プラグインを入れるだけです。
現在PubSubHubbubに対応させるためのプラグインは2つ出ていて、どちらも最新の2.8.4に対応していますし、機能的には同じですので好みで導入しましょう。

ハブのURLを登録するだけの簡単な設定画面で、しかもすでに2つのハブが登録されているので特に設定することはありません。

プラグインを有効にすると、出力されたAtomに
<atom:link rel="hub" href="http://pubsubhubbub.appspot.com"/>
<atom:link rel="hub" href="http://superfeedr.com/hubbub"/>
とハブへのリンクが追加されます。

ここから以下の流れで更新の通知がされるようになります。

  1. この新しいフィードを取得したPubSubHubbubに対応したRSSリーダーがハブへのリンクを読み取って、ハブに購読の依頼を行います。
  2. ハブは本当にRSSリーダーが依頼してきたのか確認します。確認ができたら次回の更新から通知するように動作します。
  3. WordPressで新しく記事が公開されたタイミングで、プラグインがハブに対して公開の通知を送信します。
  4. ハブは本当に記事が公開されたのか確認します。確認ができたら購読の依頼があったRSSリーダーへ通知を行います。

PubSubHubbubの実力

実際に前回の投稿がどのようになったかを追ってみました。

まず投稿後、ハブからチェックがきます(apacheのログ)。

64.233.172.18 - - [12/Sep/2009:18:42:23 +0900] "GET /feed HTTP/1.1" 200 14246 "-" "AppEngine-Google; (+http://code.google.com/appengine; appid pubsubhubbub)"

ハブのデバッグツールでハブにどのように登録されているか確認してみると、

ハブのデバッグツールのキャプチャ

無事登録されているようです。(クリックで拡大)

ではFriendFeedの方はというと、

FriendFeedのキャプチャ

WordPressの投稿ボタンを押して、登録が終わってから一拍おいてすぐ画面に現れたくらいの感覚です。

さて、本題のTwitterは

Twitterのキャプチャ

素晴らしい!画面の更新タイミングに影響していますがが、ほぼリアルタイム。

その他のPubSubHubbub対応サービス

FriendFeed以外にも、Google ReaderGoogle Alertslivedoor Readerなどが対応していて、確認できるGoogle Readerとlivedoor Readerでも確認してみました。

Google Reader

PubSubHubbubへの対応という記事は共有機能からということだったので、対応していないかもしれないと思ったんですが、

Google Readerのキャプチャ

時差なしできっちり更新に上がってきました。(クリックで拡大)

livedoor Reader

こちらは更新がされず…。

ライブドアリーダーのキャプチャ

PubSubHubbub対応後にlivedoor Readerのクローラが来ていることは確認しているので、腑に落ちないところです。

(※ 最初の更新でLDRがそのブログのpubsubhubbub対応を検出し,次の更新から更新情報を受けるようになるので,二回目の更新から反映が最速になります。)
PubSubHubbub で最速に更新を通知しつつ,舌をかまないためのテスト : nabokov7; rehash – livedoor Blog

ということなので、この更新から反映されるようになるかもしれません。
今回は確認できず。

2009/9/15 追記
やはりlivedoor Readerではすぐに更新されませんでした。
時間が経ってからの更新だとクロールによる更新なのか、ハブからの通知によるものなのか判断できないので、やはり確認できず。
livedoor Readerでちゃんとリアルタイムで更新されてるというパブリッシャーはいらっしゃるんでしょうか。livedoor Blog以外で。
ざっと調べてみたところ実践している方はいらっしゃいましたが、やはりリアルタイムでの更新は確認できていないようです。

わかりにくいPubSubHubbub

読み方の話ではなく。
複数の要素が絡まっているため、仕様が明らかとは言え、ブログ側からするとハブやRSSリーダーの挙動がわからず、更新が通知されない場合に理由がわかりにくい側面があります。

例えば、ハブへ購読依頼をするRSSリーダーが、エントリーが更新されているかどうかにかかわらず、フィード内にハブへのリンクを検出したらハブへ購読依頼をする仕様なのか(FriendFeedはこちらのようです)、エントリーが更新されたタイミングで検出・通知する仕様なのか(livedoor Readerはこちらのようです)、などです。

FriendFeedは前者のようなので、PubSubHubbub対応を行った後に、1度フィードを手動で読み込ませれば次のポストから反映されるはずです。

FriendFeedでフィードの手動取得

設定→サービス 追加/編集→マイサービスのリンクをクリック→Blogを更新(クリックで拡大)

まだまだ新しい仕組みなので、確立されるまでもう少し時間がかかるかもしれませんが、多くのサービスが対応してさらに便利になることを望みます。

参考サイト

FriendFeed→Twitterの詳しい説明が載っています。

PubSubHubbubに関する参考サイト

Tagged with:
9月 07

WordPressのコメントをOpenID対応にしました。
導入目的はコメント書き込みの敷居をいかに下げるかです。

目次

2系→3系の変更点

mixi OpenIDをWordPressで利用する方法 でも紹介しましたが、「WP-OpenID」プラグインが2系から3系に上がり、名称も「OpenID」プラグインになり、コメントフォームに含めるOpenIDのフィールド名が変わりました。

WP-OpenIDプラグイン 2系
<input type="text" name="openid_url" id="openid_url" />

OpenIDプラグイン 3系
<input type="text" name="openid_identifier" id="openid_identifier" />

設定

設定は、敷居を下げる目的なので、
「Don’t require name and e-mail for comments left with verified OpenIDs」をチェックします。
OpenIDプロバイダーになるつもりはないので「OpenID Provider Options」のチェックは全てはずしておきます。

カスタマイズ

標準ではドーンとOpenID(URL)を入力するフォームを用意するだけの簡素なものなので、あらかじめ主要なOpenIDプロバイダーへのログインボタンを用意したものへとカスタマイズしました。

コメント欄キャプチャ

OpenID認証を利用した場合は、名前とメールアドレスの入力を省くことができる設定にしたので、既存のコメントフォームにOpenIDを入力するフォームだけ追加してもユーザビリティーが悪くなります。

対応策として、「通常のコメントフォーム」と「OpenID対応コメントフォーム」を用意して、デフォルトはOpenID対応、クリックによって通常とOpenID対応がtoggleする仕様にしました。

各(X)HTMLにclassとidを付けます。

  • OpenID対応のコメントフォーム(form)にid=”openid_commentform”を設定
  • OpenID対応のコメント欄(textarea)にid=”openid_comment”を設定
  • 各OpenIDプロバイダーの画像にclass=”openid_submit”、それぞれ個別にidを付ける
  • toggleのスイッチになるリンクにid=”comment_toggle”を設定

javascript(jQuery)を追加します。

jQuery(document).ready(function(){

    jQuery(".openid_submit").click(function(){
        if(jQuery('#openid_comment').val() == ''){
            alert('コメントを入力してください。');
            return false;
        }
        var openid_target= jQuery(this).attr('id');
        var openid_url = '';
        if(openid_target == 'mixi'){
                openid_url = 'https://mixi.jp/';
        }else if(openid_target == 'google'){
                openid_url = 'https://www.google.com/accounts/o8/id';
        }else if(openid_target == 'yahoo'){
                openid_url = 'http://yahoo.co.jp/';
        }else if(openid_target == 'livedoor'){
                openid_url = 'http://livedoor.com/';
        }else if(openid_target == 'hatena'){
                var hatena_id = window.prompt("はてなIDを入力して下さい。","");
                if(hatena_id == null) return false;
                openid_url = 'http://www.hatena.ne.jp/'+hatena_id+'/';
        }else if(openid_target == 'other'){
                var open_id = window.prompt("OpenID(URL)を入力して下さい。","");
                if(open_id == null) return false;
                openid_url = open_id;
        }

        if(openid_url == ''){
            alert('不正なOpenIDです。');
            return false;
        }

        jQuery('#openid_identifier').val(openid_url);
        return true;
    });
    jQuery(".openid_submit").mouseover(function(){
        jQuery(this).css('border-color','#e6db55 #dfcd2a #dfcd2a #e6db55');
    });
    jQuery(".openid_submit").mouseout(function(){
        jQuery(this).css('border-color','#CCCCCC #AAAAAA #AAAAAA #CCCCCC');
    });

    jQuery("#commentform").hide();
    jQuery("#comment_toggle").toggle(
            function(){
                jQuery("#openid_commentform").hide();
                jQuery("#commentform").show();
                jQuery('#comment_toggle').text("OpenID認証のコメント欄に切り換え");
            },
            function(){
                jQuery("#commentform").hide();
                jQuery("#openid_commentform").show();
                jQuery('#comment_toggle').text("通常のコメント欄に切り換え");
            }                
    );
});

PHP5.3.0でハマった

このサーバーではWordPressをPHP5.3.0で動かしています。
OpenIDプラグインはPHP OpenID Libraryを内包していて、OpenIDにかかわる実装はこのライブラリに依存していますが、これが5.3.0に対応していないため、素のままでは動きません。

問題の解説と解決方法はこちらのブログが詳細ですので、ご覧ください。
PHP 5.3: 参照渡しの関数/メソッドを定義してた人は call_user_func_array に注意 – 肉とご飯と甘いもの @ sotarok

PHP OpenID Libraryのオフィシャルでも5.2.4までしかテストしてないよと言っているので仕方ないところです。
修正箇所が3箇所なので、パッチを作成しました。パスを変えれば、PHP OpenID Libraryのパッチとしても動くはずです。
wordpress-openid-3.2.3-cloudrop-090907.patch.tar.gz

それから、id:sotarokさんが触れられている時とドキュメントが変わってましたので、触れておきます。

下位互換性のない変更点

旧ドキュメント

引数を参照渡しする関数に値を渡した場合の振る舞いが変更されました。 以前は値渡しとして引数を受け取っていましたが、5.3.x からは warning が生成され、 全ての参照渡しのパラメーターが NULL となります。

現在のドキュメント

引数を参照渡しする関数に値を渡した場合の振る舞いが変更されました。 以前は値渡しとして引数を受け取っていましたが、今は fatal error が発生するようになりました。 参照渡しを期待している関数に定数やリテラルを渡していたコードは、 いったんその値を変数に代入してから関数に渡すよう書き換える必要があります。

Tagged with:
preload preload preload