异步执行的 Gravatar 缓存

8 月 23 日, Ray Chow 发表于 小技巧, 96 回应

话说鄙人超级喜欢跟风,谁写出一部技巧集,我就能山寨再加以修改写出第二部 :twisted: 。把 Gravatar 头像缓存到本地服务器是很多博客会做的事情,那是因为国内糟糕的网路环境访问 Gravatar 网站特别慢,某些地区甚至与 Gravatar 绝缘。

不少人用的是 Willin Kan 的缓存 Gravatar 头像之方法,我昨天在本地调试主题的时候加上了这段代码,但发现了一个比较严重的问题:评论列表要等很长的时间才能显示,甚至直接 30 秒超时。原因是原代码中这样一句(第 13 行):

copy($g, $e);

copy 函数起到了把 Gravatar 头像复制到本地的作用,此时 PHP 解释器只能等待复制完成才能继续运行,反而成为拖慢速度的隐患。

本文适用于以下情况:

  • 使用国内服务器但想缓存 Gravatar 头像;
  • Gravatar 服务器出现问题时。

国外服务器由于访问 Gravatar 服务器很快,因此不会有明显的延迟感,但当以上情况时延迟就非常明显了。

该怎么解决这个问题呢?某个 Gravatar 头像第一次被显示时,实际上仍然是调用 Gravatar 服务器的资源,与此同时博客的服务器再从 Gravatar 服务器将头像下载下来。博客服务器下载头像的过程对用户访问是没有任何意义的,应该将 copy 过程放在另一个线程执行,评论列表输出不受影响,问题就解决了。

PHP 本身是不支持多线程的,我们可以将下载头像的代码写在一个单独的 PHP 文件中,然后由处理评论列表的函数发起一个到此文件的请求,并立即关闭连接,继续向下执行。被请求的文件进行下载工作就相当于异步执行了。

下面给出具体实行方法。

一、建立异步执行的 PHP 文件

在 WordPress 根目录或者其它文件夹新建 gravatar.php(也可以改成您喜欢的名字),内容如下:

if ( $_SERVER['REQUEST_METHOD'] != 'POST' ) {
    header( 'Allow: POST' );
    header( 'HTTP/1.1 405 Method Not Allowed' );
    header( 'Content-Type: text/plain' );
    exit;
}
$key = '123456'; // $key 为防止恶意提交的验证码
if ( !isset( $_POST['hash'], $_POST['s'], $_POST['key'] ) || $_POST['key'] != $key ) {
    header( 'HTTP/1.1 400 Bad Request' );
    header( 'Content-Type: text/plain' );
    exit;
}
$hash = $_POST['hash'];
if ( !ctype_alnum( $hash ) || strlen( $hash ) != 32 )
    return;
$s = strtoupper($_POST['s']);
if ( !ctype_digit( $s ) ) return;
$r = isset( $_POST['r'] ) ? $_POST['r'] : '';
$rating = array( '', 'G', 'PG', 'R', 'X' );
if ( !in_array( $r, $rating ) )
    return;
if ( defined( 'ABSPATH' ) )
    require_once( ABSPATH . 'wp-load.php' );
else
    require_once( './wp-load.php' ); // 要用相对路径指向 WordPress 根目录,如果放在主题目录下,则改为 ../../../wp-load.php
$d = get_bloginfo( 'wpurl' ) . '/wp-content/avatar/default.png'; // 缺省头像的位置
$gravatarpath = "http://www.gravatar.com/avatar/";
$gravatarpath .= $hash;
$gravatarpath .= '?s=' . $s;
$gravatarpath .= '&d=' . urlencode( $d );
if ( !empty( $r ) )
    $gravatarpath .= '&r=' . $r;
$cachepath = ABSPATH . 'wp-content/avatar/' . $hash; // 缓存头像保存的位置
copy( $gravatarpath, $cachepath );
if ( filesize( $cachepath ) < 500) copy( $d, $cachepath );

几点说明:

  1. 第 8 行中 $key 为防止恶意提交的验证码,请改成不容易破解的字符组合,也不要告诉他人;
  2. 如果文件不是放在根目录,需要修改第 24 行的相对路径,距离根目录多少层就打入多少 ../,主题目录为 /wp-content/themes/theme,共有 3 层则为 ../../../wp-load.php;
  3. 没有为缓存的头像指定扩展名,因为头像可能有多种格式。实际上也不需要扩展名。

二、建立 Gravatar 头像存放的文件夹

根据刚才设定的缓存头像保存位置新建文件夹,例如代码中将 Gravatar 头像存放在 wp-content/avatar 文件夹下,因为我认为 wp-content 文件夹是用来存放用户生成内容的。

请将此文件夹的权限(chmod)设定为 755。

将无 Gravatar 头像时显示的缺省头像放在新建的文件夹中,文件名要与上面代码中「缺省头像位置」一行一致。

三、写入 functions.php

打开主题目录下的 functions.php,在 <?php 与 ?> 之间插入以下代码,如果你已经用了 Willin Kan 的代码,请替换:

function my_avatar( $email, $size = '40', $default = '', $alt = false ) {
    $alt = ( false === $alt ) ? '' : esc_attr( $alt );
    $hash = md5( strtolower( $email ) );
    $wpurl = get_bloginfo( 'wpurl' );
    $out = $wpurl . '/wp-content/avatar/' . $hash; // 缓存头像的 URL
    $cachepath = ABSPATH . 'wp-content/avatar/' . $hash; // 缓存头像位于硬盘的位置
    $t = 1209600; // 设定 14 天, 单位: 秒
    $default = $wpurl . '/wp-content/avatar/default.png'; // 缺省头像的位置
    if ( !is_file( $cachepath ) || ( time() - filemtime( $cachepath ) ) > $t ){ // 当头像不存在或文件超过 14 天才更新
        $rating = get_option( 'avatar_rating' );
        $gravatarpath = "http://www.gravatar.com/avatar/$hash?s=$size&d=$default&r=$rating";
        $key = '123456'; // $key 为刚刚用于异步下载的 PHP 文件中设定的验证码
        $fp = fsockopen( $_SERVER['HTTP_HOST'], 80, &$errno, &$errstr, 5 ); // 建立 Socket 连接
        if ( $fp ) {
            $encoded = "hash={$hash}&s={$size}&r={$rating}&key=" . urlencode( $key );
            fputs( $fp, "POST ".get_bloginfo('wpurl')."/wp-content/themes/beam/gravatar.php HTTP/1.0\n" ); // 用于异步下载的 PHP 文件 URL
            fputs( $fp, "Host: ".$_SERVER['HTTP_HOST']."\n" );
            fputs( $fp, "Content-type: application/x-www-form-urlencoded\n" );
            fputs( $fp, "Content-length: " . strlen($encoded)."\n" );
            fputs( $fp, "Connection: close\n\n" );
            fputs( $fp, "$encoded\n" );
            fclose( $fp );
        }
        $out = esc_attr( $gravatarpath );
    }
    $avatar = "<img title='{$alt}' alt='{$alt}' src='{$out}' class='avatar avatar-{$size} photo' height='{$size}' width='{$size}' />";
    return apply_filters( 'my_avatar', $avatar, $email, $size, $default, $alt );
}

几点说明:

  1. 请将 $key 改为与第一步中指定的验证码一致,否则程序不能工作;
  2. 该函数调用方式与 WordPress 自带 get_avatar 已不相同;
  3. get_avatar 语法:
    get_avatar( $id_or_email, $size = '<size>', $default = '<path_to_url>' , $alt = '<alt>' );

    my_avatar 语法:

    my_avatar( $comment->comment_author_email, $size = '<size>', $default = '<path_to_url>' , $alt = '<alt>' );

    您也可以将 WordPress 自带 get_avatar 复制并改造以保持原有功能;

  4. 按照以上变更的调用方式,将所有主题模板中出现的 get_avatar() 修改为 my_avatar(),「大概是 functions.php,comments.php,sidebar.php,comments-ajax.php 会有头像的地方有 get_avatar() 函数。」

四、测试

如果把文件放在与本文给出的位置不一样的地方,要修改的地方会比较多,最好在本地测试完成后上传到服务器。

P.s. 我知道代码花眼了点……想办法找个好用的代码高亮插件

无觅相关文章插件,快速提升流量

固定链接: http://beamnote.com/2010/asynchronous-implementation-of-the-gravatar-cache.html

订阅本文 RSS 以获取最新动向。

发表评论或者发送 Trackback

96 回应

  1. 是换模板了么?
    我还没遇到显示Gravatar很慢的,速度上貌似没什么问题···

  2. 我这个还没弄,回头好好研究下!

  3. 想用,可是驴头不对马嘴,博客语言不一样, :arrow:

  4. 那两个小尔朵朵也被你弄来了啊 :lol: :lol:

  5. zhy

    如果不这样修改,那国内主机不用avatar缓存不是比用了更快?
    现在不少人都不用了

  6. MK

    好像我的没这种想象的…… :!:

  7. Ray

    貌似很完美了,嘿嘿!

  8. 异步执行 这个很强悍 一点都不影响速度额~

发表评论

必填区域以 * 标出

*
*