package Sledge::Plugin::CacheContent;
use strict;
use vars qw($VERSION);
$VERSION = 0.03;
use Apache::File;
use Apache::Constants;
use Digest::MD5 qw(md5_hex);
use File::Spec;
use File::Path;
use vars qw($Debug);
$Debug = 0 unless defined $Debug;
sub import {
my $class = shift;
my $pkg = caller;
if ($pkg->isa('Sledge::Pages::Base')) {
$pkg->register_hook(
AFTER_INIT => sub {
my $self = shift;
unless ($self->is_post_request) {
$self->add_filter(\&capture_output);
}
},
);
}
}
sub capture_output {
my($self, $content) = @_;
my($dir, $file) = _map_filepath($self->r);
warn "dir is $dir, file is $file" if $Debug;
mkpath $dir, 0, 0777 unless -e $dir;
my $fh = Apache::File->new("> $dir/$file") or die "$dir/$file: $!";
print $fh $content;
close $fh;
my $ct = $self->r->content_type;
warn "ct is $ct" if $Debug;
my $out = Apache::File->new("> $dir/$file.ct") or die "$dir/$file.ct: $!";
print $out $ct;
close $out;
return $content;
}
sub _map_filepath {
my $r = shift;
my $url = _build_url($r);
my $key = $r->dir_config('SledgeCacheByUserAgent')
? join("\t", $url, agent_name($r)) : $url;
my $digest = md5_hex($key);
my $base_dir = $r->dir_config('SledgeCacheDir') || '/tmp/Sledge-Plugin-Cache';
my $dir = File::Spec->catfile($base_dir, substr($digest, 0, 1), substr($digest, 1, 1));
return $dir, $digest;
}
sub agent_name {
my $r = shift;
require HTTP::MobileAgent;
return HTTP::MobileAgent->new($r)->name;
}
sub _build_url {
# same with Sledge::Pages::Base::current_url()
my $r = shift;
my $scheme = $ENV{HTTPS} ? 'https' : 'http';
my $url = sprintf '%s://%s%s', $scheme, $r->header_in('Host'), $r->uri;
$url .= '?' . $r->args if $r->args;
return $url;
}
sub handler {
my $r = shift;
return DECLINED if ($r->main || $r->method eq 'POST');
my($dir, $digest) = _map_filepath($r);
my $file = "$dir/$digest";
my $timeout = $r->dir_config('SledgeCacheTTL') || 60;
if (-f $file && -M _ < ($timeout / (24 * 60))) {
# leave it to Apache default handler
warn "using cache file $file" if $Debug;
my $in = Apache::File->new("$dir/$digest.ct");
# don't care even if it doesn't exist
if ($in) {
my $ct = <$in>;
close $in;
warn "restored ct is $ct" if $Debug;
$r->content_type($ct);
}
$r->filename($file);
$r->handler('default-handler');
return OK;
}
# don't use cache this time
warn "don't use cache this time" if $Debug;
return DECLINED;
}
1;
__END__
=head1 NAME
Sledge::Plugin::CacheContent - Generate and serve cached content
=head1 SYNOPSIS
PerlTransHandler Sledge::Plugin::CacheContent
SetHandler perl-script
PerlHandler Apache::Registry
PerlSetVar SledgeCacheDir /home/sledge/cache
PerlSetVar SledgeCacheTTL 120
PerlSetVar SledgeCacheByUserAgent 1
# in your Pages class
use Sledge::Plugin::CacheContent;
=head1 DESCRIPTION
Sledge::Plugin::CacheContent はSledgeにより作成されるコンテンツを静的
にディスク出力し、TTLの範囲内で静的にサーブするmod_perl ハンドラです。
ユーザごとに変化しないコンテンツなどを出力する際にパフォーマンスの大き
な向上がのぞめます。mod_perl を EVERYTHING=1 でコンパイルしている必要
があります。
キャッシュの単位はURL(QUERY_STRING ふくむ)をベースとしていますので、
/news/article.cgi?id=1
/news/article.cgi?id=2
などは別のものとしてキャッシュされます。
=head1 OPTIONS
以下のオプション項目をPerlSetVarを利用して設定できます。
=over 4
=item SledgeCacheDir
キャッシュファイルが出力されるディレクトリ。デフォルトは /tmp/Sledge-Plugin-CacheContent
=item SledgeCacheTTL
キャッシュの生存期間(単位:分)。デフォルトは60
=item SledgeCacheByUserAgent
UserAgentごとにキャッシュを生成するオプション。携帯端末向けなど、フィルタリング
している場合に便利です。(フィルタリングが完了した後にキャッシュするよ
うに、このモジュールの use はフィルタ系プラグインの最後にしておく必要があります)
HTTP::MobileAgent モジュールが別途インストールされている必要があります。
=back
=head1 NOTE
初回アクセス時にキャッシュファイルを出力し、2度目以降はTTLをチェック後、
そのファイルをApacheのデフォルトハンドラにて出力するしくみになっていま
す。TTLチェックが失敗(TTLをオーバーしている)場合には、再度動的生成を行
いキャッシュファイルを更新します。
実際に Hello World! によるベンチマークをとってみると、(ab -n100 -c5でベンチ)
CacheContent使用
Requests per second: 134.95 [#/sec] (mean)
Time per request: 37.05 [ms] (mean)
CacheContent使用せず
Requests per second: 27.26 [#/sec] (mean)
Time per request: 183.40 [ms] (mean)
のように劇的にパフォーマンスが向上しています。
=head1 AUTHOR
Tatsuhiko Miyagawa Emiyagawa@edge.jpE
This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
=head1 SEE ALSO
L
=cut