読者です 読者をやめる 読者になる 読者になる

エロサイトの作り方

2013年11月から勉強しながらエロサイトを作っています。

DISABLE_WP_CRONをしてもNginx Cache Controllerプラグインがwp-cron.phpにリクエストし続ける件

Nginx WordPress

2014/09/25 追記

この記事がプラグイン作者に補足された結果、現バージョン(2.8.0)では修正されています。やったね!

沼にハマってほぼ徹夜でございます。とほほ。

コトの発端

エロいサイトというのは夜が中心なのですが、その中でも週末は特に盛り上がるわけですよ。

で、週末になるとサイトが重たくなるので、金曜までに何とかしないとなと思ってやったのがここ最近の記事。

この時はピーク時のLoad Averageが2.77→1.52まで下がったので、 期待したほどの効果じゃないなーと思いながらも、これでよしとしたわけです。

そして金曜日になり、モニタリングしてたら、

f:id:hentai-kun:20140830065318p:plain

金曜なのでDisk I/OとNetwork I/Oが高いのはまあわかるとして、CPU負荷がピークで79.2%とかなり高い。

しかもプロセス別で見るとPHPがピークで53.5%を占めている。

リバースプロキシーを使っているので、PHPへのアクセスは、

  • キャッシュの有効期限が切れた
  • 予約投稿時間になりキャッシュがクリアされた(直近の投稿は21:40)
  • RAMディスクがいっぱいになってキャッシュが消された
  • cronからのcurlを使ったwp-cron.phpへのアクセス(2分おき)

くらいしか発生しないはずのため、この数字はおかしいと思った。

発生箇所探し

ここはさっくりとNginxのログを見たらすぐわかったのですが、wp-cron.phpへのアクセスが一般ユーザー経由っぽいのが混じってるんですね。

xxx.xxx.xxx.xxx - - [30/Aug/2014:00:08:01 +0900] "GET /wp-cron.php HTTP/1.0" 200 0 "http://hentai-kun.example.jp/archives/6281" "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.0; Trident/5.0)"
xxx.xxx.xxx.xxx - - [30/Aug/2014:00:08:01 +0900] "GET /wp-cron.php HTTP/1.0" 200 0 "-" "curl/7.22.0 (x86_64-pc-linux-gnu) libcurl/7.22.0 OpenSSL/1.0.1 zlib/1.2.3.4 libidn/1.23 librtmp/2.3"
xxx.xxx.xxx.xxx - - [30/Aug/2014:00:08:14 +0900] "GET /wp-cron.php HTTP/1.0" 200 0 "http://hentai-kun.example.jp/archives/1890" "Mozilla/5.0 (iPhone; CPU iPhone OS 7_1 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) GSA/4.1.0.31802 Mobile/11D167 Safari/9537.53"
xxx.xxx.xxx.xxx - - [30/Aug/2014:00:08:40 +0900] "GET /wp-cron.php HTTP/1.0" 200 0 "http://hentai-kun.example.jp/archives/6666" "Opera/9.80 (Windows NT 6.1; WOW64) Presto/2.12.388 Version/12.17"
xxx.xxx.xxx.xxx - - [30/Aug/2014:00:08:49 +0900] "GET /wp-cron.php HTTP/1.0" 200 0 "http://hentai-kun.example.jp/" "Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko"

2行目のリファラーがcurl/7.22.0で始まっているものがcronからのアクセスで、残りはどう見ても一般ユーザーから。

Ajaxwp-cron.phpへのアクセスを投げているっぽいので、レンダリングされたソースを見たところ、

<script type="text/javascript">
(function($){
    $.get("http://hentai-kun.example.com/wp-cron.php");
})(jQuery);
</script>

という記述があった。

どうやら、wp_footer()で書かれたものらしい。

wp_footer()の謎

ここまでは良かったのですが、ここから詰まってしまった。

そもそも、wp_footer()って何を書き出しているの?、と。

そしてここからWordPressのソース追っかけ。

wp_footer()
do_action( 'wp_footer' );
add_action('wp_footer')
add_action( 'wp_footer',           'wp_print_footer_scripts',         20    );
wp_print_footer_scripts()
do_action( 'wp_print_footer_scripts' );
add_action('wp_print_footer_scripts')
add_action( 'wp_print_footer_scripts', '_wp_footer_scripts'                 );
_wp_footer_scripts()
print_late_styles();
print_footer_scripts();

wp_footerwp_print_footer_scriptsのアクションを呼び出しているようだ。

しかし、うーん、わからん。

wp-cron.phpで検索

wp_footer()はひとまず置いておいて、どうせどこかに文字列コーディングされているだろうから、 wp-corn.phpで検索したらわかるのではないかと思って調べてみる。

そうしたら、wp-includes/cron.phpの2つの関数が引っかかった。

wp_cron()
// Prevent infinite loops caused by lack of wp-cron.php
if ( strpos($_SERVER['REQUEST_URI'], '/wp-cron.php') !== false || ( defined('DISABLE_WP_CRON') && DISABLE_WP_CRON ) )
  return;

1つ目のwp_cron()は、開始早々defined('DISABLE_WP_CRON')が定義されてたら終了する処理だったので関係ない。

spawn_cron()

もう1つのspawn_cron()は、

$cron_request = apply_filters( 'cron_request', array(
  'url'  => add_query_arg( 'doing_wp_cron', $doing_wp_cron, site_url( 'wp-cron.php' ) ),
  'key'  => $doing_wp_cron,
  'args' => array(
    'timeout'   => 0.01,
    'blocking'  => false,
    /** This filter is documented in wp-includes/class-http.php */
    'sslverify' => apply_filters( 'https_local_ssl_verify', true )
  )
) );

wp_remote_post( $cron_request['url'], $cron_request['args'] );

ここら辺の処理があやしいものの、wp_cron()からしか呼ばれないのでここも実行されなそう。

もしかして、DISABLE_WP_CRONが効いてないのでは?

と思って、wp_cron()

function wp_cron() {
  return;
  ...
}

のようにいきなりreturnにしてみたところ、変わらず。

うーん、どうやらWordPress本体で処理しているものではなさそう。

ということは、プラグインが怪しい

と思って、プラグインwp-cron.phpで検索したところ、 Nginx Cache Controllerプラグインで、

public function wp_print_footer_scripts_wp_cron(){
    $js = '
<script type="text/javascript">
(function($){
    $.get("%s");
})(jQuery);
</script>
';
    $js = sprintf($js, site_url('wp-cron.php'));
    echo apply_filters('wp_print_footer_scripts_wp_cron', $js);
}

という記述を見つける。間違いなくこれだ!

Nginx Cache Controller

というわけで、ソースの追っかけ。

wp_print_footer_scripts_wp_cron()
if ($this->is_future_post()) {
    wp_enqueue_script('jquery');
    add_action(
        "wp_print_footer_scripts",
        array(&$this, "wp_print_footer_scripts_wp_cron")
    );
}
add_action('wp_print_footer_scripts')
public function wp_enqueue_scripts() {
    ...
    if ($this->is_future_post()) {
        wp_enqueue_script('jquery');
        add_action(
            "wp_print_footer_scripts",
            array(&$this, "wp_print_footer_scripts_wp_cron")
        );
    }
}
wp_enqueue_scripts()
add_action(
    'wp_enqueue_scripts',
    array($this, 'wp_enqueue_scripts')
);

wp_enqueue_scriptアクションで、wp_print_footer_scriptsアクションを定義して、 その中でwp-cron.phpのリクエストのコードを出力しているようだ。

remove_action('wp_print_footer_scripts')で消す(失敗)

対応としては、add_action()しているものにremove_action()をかければいいわけだけど、 remove_action()には引数として実行する関数名を指定しないといけない。

しかし、

add_action(
    "wp_print_footer_scripts",
    array(&$this, "wp_print_footer_scripts_wp_cron")
);

なんか配列だし、thisの参照を渡しているし、これをどうやって指定すればいいのか?

add_actionの第2引数が配列な理由

add_action フックをクラスを使って構築されたプラグインやテーマ内で使うには、add_action コール内にそのクラスの関数名とともに $this を追加してください。

関数リファレンス/add action - WordPress Codex 日本語版

どうやら、クラスのインスタンスに含まれる関数を呼ぶ場合はarrayを使うということらしい。

以下はその例です。

class MyPluginClass {
    public function __construct() {
         //add your actions to the constructor!
         add_action( 'save_post', array( $this, 'myplugin_save_posts' ) );
    }
    public function myplugin_save_posts() {
         //do stuff here...
    }
}

なるほど。

functions.phpに追記

というわけで、必要な処理はこうなりました。

add_action('wp_enqueue_scripts', 'remove_nginxchampuru_cron', 100);
function remove_nginxchampuru_cron() {
  global $nginxchampuru_cache;
  remove_action('wp_print_footer_scripts', array(&$nginxchampuru_cache, 'wp_print_footer_scripts_wp_cron'));
}

wp_enqueue_scriptでNginx Cache Controllerが処理した後で、wp_print_footer_scripts_wp_cronを除去しています。

これで、wp-cron.phpが過剰に呼ばれなくなった。

しかし……

本来は、

if ($this->is_future_post()) {
    wp_enqueue_script('jquery');
    add_action(
        "wp_print_footer_scripts",
        array(&$this, "wp_print_footer_scripts_wp_cron")
    );
}

が、

if ($this->is_future_post() && (!defined('DISABLE_WP_CRON') || !DISABLE_WP_CRON)) {
    wp_enqueue_script('jquery');
    add_action(
        "wp_print_footer_scripts",
        array(&$this, "wp_print_footer_scripts_wp_cron")
    );
}

こうなっているべきだよね。(たぶん)