从天涯论坛看终极页的缓存控制
一般不太上天涯论坛灌水或者潜水,不过经常去天涯SA刘天斯的blog上逛逛~在他开源memlink后,想起来去天涯看看前端设计,发现其论坛主列表页采用nginx发布(预计有nginx的module直接读取memlink),终极页前端采用varnish缓存,回复时的动态asp页面由IIS处理。但在终极页上,虽然显示的server也还是IIS,我却有一定的怀疑~
在访问某终极页时,可以看到类似如下的header
Age:78
Cache-Control:public
Connection:close
Content-Encoding:gzip
Content-Length:34687
Content-Type:text/html
Date:Fri, 10 Dec 2010 09:03:48 GMT
Expires:Fri, 10 Dec 2010 09:07:30 GMT
Last-Modified:Fri, 10 Dec 2010 09:02:30 GMT
Server:Microsoft-IIS/6.0
Vary:Accept-Encoding
Via:Tianya Cache
X-Cache:HIT118
X-Powered-By:ASP.NET
X-tianya:1098678484 1098675384
如果按下F5,会看到一个IMS请求,最后返回304或者200的结果。
如果按下Ctrl+F5,会看到一个no-cache请求,最后返回200的结果。
不过奇怪的是,在no-cache请求后,虽然明知页面没有变(请求的是一个多页帖子的第一页,wget和wget –header ‘Cache-Control: no-cache’下来后的页面的MD5值都一样),但返回的last-modified时间却变成了和Date一致的当前时间了。以至于让我怀疑这个*.shtml难道不是静态化生成的?
然后回复该帖。通过POST方式向另一个动态域名传输数据,并302跳转回原页面。由于POST方式的不可缓存性,浏览器自动带上了no-cache请求头,并传递给了302之后的动作,即以no-cache重新请求了原帖子的url,并重新下载了该页面。由此完成了对回复的即时更新——对于论坛来说,重要的就是发帖人自己能即时看到,其他人完全可以等一会页面过期或者IMS比对来看别人的新回复。
假设前面说到的shtml确实是静态化生成的话,那么这个直接跳转的做法就有一定的风险,即要求系统在极短时间(从浏览器时间看就是POST的firstbyte时间开始,到200的connection时间结束,网络较好的情况下应该是毫秒级)内,完成对终极页的静态化工作。
写到这里,愈发怀疑这个shtml是asp的伪装版了……
nagios的add-ons安装小抄~
给nagios安装几个add-ons,碰到一些一般安装教程上不会提及的问题,记录一下:
1、ndoutils:
在./configure通过后make一直error,因为不管是否–with-mysql-lib,也不管–with-mysql-lib=/usr/local/mysql/lib还是/usr/local/mysql/lib/mysql,甚至使用LDFLAGS=-I/usr/local/mysql/lib等等,最后在make的时候总还是会报出如下错误:
../include/config.h:261:25: error: mysql/mysql.h: No such file or directory
../include/config.h:262:26: error: mysql/errmsg.h: No such file or directory
解决办法:编辑config.h文件的261和262行,把mysql/*.h的mysql/删除掉即可。
make完成后,将相应文件cp到指定目录,启动ndomod会报错libmysqlclient.so.6.0.0动态链接库无法找到。网上一般都说ln -s /usr/local/mysql/lib/* /usr/lib;echo ‘/usr/lib’ >> /etc/ld.so.conf;ldconfig即可。其实还不行。
解决办法:echo ‘/usr/local/mysql/lib/mysql’ > /etc/ld.so.conf.d/mysql.conf;ldconfig即可。因为通用办法的目录不够深。
2、pnp4nagios:
之前使用的pnp0.4.*版本,今天下的是pnp0.6.*版本。整个url设计发生了较大变化。各监控项页面的url从pnp4nagios/index.php?host=&service=变成了/pnp4nagios/graph?host=&service=。而这个graph(rrd图像的url是/pnp4nagios/image?***)则通过apache的mod_rewrite实现。
pnp自己编译时可以make出来一个httpd.conf。其中相关Rewrite的包括:
<Directory ‘”/usr/local/pnp4nagios/share/”>
RewriteEngine On
RewriteBase /pnp4nagios/
RewriteRule ^(application|modules|system) – [F,L]
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* index.php$0 [PT,L]
</Directory>
因为pnp编译时没有具体区分etc和share的路径,所以之后apache的发布路径也不同,为了方便,不再写directory。最后经过反复试验,可用配置如下:
RewriteEngine On
RewriteRule ^(application|modules|system) – [F,L]
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-d
RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f
RewriteRule ^/nagios/pnp4nagios/(.*) /nagios/pnp4nagios/index.php$1 [PT,L]
试验中发现几个apache与nginx的不同:
1、rewriterule的转向url不能用^标记起始端!
2、PT的强制进入下一个处理器相当有用,不然会形成回环rewrite!
3、rewritecond里德%{REQUEST_FILENAME}默认是在rewritebase下的——而rewritebase只能在directory里使用。
域名切换&SEO
}
root /www/old.domain.com;
url_rewrite配置的小区别
apache防盗链(mod_perl试用)
在web请求视频时,按算法生成密文和明文串,然后依规则组成最终的url请求;
算法规则——用如下三个关键词生成MD5密文:
1、自定义密钥:abcde.;
2、视频文件真实路径,即/path/to/file.rmvb;
3、请求时间,以当前UNIX时间换算为十六进制字符串,并作为明文;
最终url格式是http://www.test.com/path/to/file.rmvb?key=1234567890abcdefghijklmnopqrstuy&t=1234abcd这样。
要求失效时间为8小时。
这个需求和之前一次相当类似,不过上回是squid,这次是apache。同样采用perl脚本进行防盗链设置,apache需要使用mod_perl模块。
首先安装perl模块:
wget http://perl.apache.org/dist/mod_perl-2.0-current.tar.gz
tar zxvf mod_perl-2.0-current.tar.gz
cd mod_perl-2.0-current.tar.gz
perl Makefile.PL MP_APXS=/home/apache2/bin/apxs
make && make
install
echo "LoadModule perl_module modules/mod_perl.so" >> /home/apache2/conf/httpd.conf
perl -MCPAN -e shell
>install Apache2::Request
>look Apache2::Request
rm -f configure
rm -f apreq2-config
./buildconf
perl Makefile.PL
make && make install
exit
(因为64位系统的libexpat.so有问题,编译libapreq2会出问题,只好如此强制安装)
echo "LoadModule apreq_module modules/mod_apreq2.so" >> /home/apache2/conf/httpd.conf
因为libapreq2.so安装在/home/apache2/lib/下了,所以需要echo "/home/apache2/lib">/etc/lo.so.conf.d/apache.conf,然后ldconfig。
修改httpd.conf,加入如下设置:
PerlPostConfigRequire /home/apache2/perl/start.pl
<Location /smg>
SetHandler modperl
PerlAccessHandler DLAuth
PerlSetVar ShareKey abcde.
</Location>
然后mkdir /home/apache2/perl/,在其中创建start.pl和DLAuth.pm两个文件。start.pl文件内容如下:
use strict;
use lib qw(/home/apache2/perl);
use Apache2::RequestIO ();
use Apache2::RequestRec ();
use Apache2::Connection ();
use Apache2::RequestUtil ();
use Apache2::ServerUtil ();
use Apache2::Log ();
use Apache2::Request ();
1;
DLAuth.pm文件内容如下:
package DLAuth;
use strict;
use warnings;
use Socket qw(inet_aton);
use POSIX qw(difftime strftime);
use Digest::MD5 qw(md5_hex);
use Apache2::RequestIO ();
use Apache2::RequestRec ();
use Apache2::Connection ();
use Apache2::RequestUtil ();
use Apache2::ServerUtil ();
use Apache2::Log ();
use Apache2::Request ();
use Apache2::Const -compile => qw(OK FORBIDDEN);
sub handler {
my $r = shift;
my $s = Apache2::ServerUtil->server;
my $shareKey = $r->dir_config(‘ShareKey’) || ”;
my $uri = $r->uri() || ”;
my $args = $r->args() || ”;
my $expire = 8 * 3600;
if ($args =~ m#^key=(w{32})&t=(w{8})$#i){
my ($key, $date) = ($1, $2);
my $str = md5_hex($shareKey . $uri . $date)
my $reqtime = hex($date);
my $now = time;
if ( $now – $reqtime < $expire){
if ($str eq $key) {
return Apache2::Const::OK;
} else {
$s->log_error("[$uri FORBIDDEN] Auth failed");
return Apache2::Const::FORBIDDEN;
}
}
}
$s->log_error("[$uri FORBIDDEN] Auth failed");
return Apache2::Const::FORBIDDEN;
}
1;
就可以了。
apachectl restart。测试一下,先用perl自己生成一个测试链接:
#!/usr/bin/perl -w
use Digest::MD5 qw(md5_hex);
my $key = "bestv.";
$path = shift(@ARGV);
my $date = sprintf("%x",time);
$result = md5_hex($key . $path . $date);
my $uri = "http://127.0.0.1$path?key=$result&t=$date";
print $uri;
运行./url.pl /smg/abc.rmvb生成http://127.0.0.1/smg/abc.rmvb?key=4fb6b4e6a0ec484aea98fa727fc7149d&t=4bc7dd5a,然后wget -S -O /dev/null "http://127.0.0.1/smg/abc.rmvb?key=4fb6b4e6a0ec484aea98fa727fc7149d&t=4bc7dd5a",返回200 OK;任意修改t为12345678,再wget,返回403 Forbidden。error_log显示如下:
[Fri Apr 16 11:47:06 2010] [error] [/smg/abc.rmvbkey=4fb6b4e6a0ec484aea98fa727fc7149d&t=12345678 FORBIDDEN] Auth failed
客户页面小故障
location.replace("http://msn.golfbox.cn/cache/874/36804.html");
</script>
curl http://msn.golfbox.cn/ |sed ‘s/\n//g’|sed ‘s/href=/\n/g’|grep "^"http://msn.golfbox.cn/cache"|awk -F’"’ ‘{print $2}’>url
for j in `cat url`;do
for i in `cat ip`;do
curl -x $i:80 $j|awk -F’"’ ‘/replace/{print "’$j’","’$i’",$2}’>>golfbox.log
# curl -x $i:80 -I $j|awk ‘/Age/{print "’$j’","’$i’",$2}’>>age.log
done
done
cat golfbox.log |awk ‘{if($1==a){if($3!=b){system("/home/squid/bin/squidclient -p 80 -h "$2" -m purge "$1)}};a=$1;b=$3}’
+'<a href="javascript:iclose();" style="float:left; margin-left:190px">关闭</a>’
+'<div style="clear:both"></div>’
+'<a href="http://new.msn.golfbox.cn/wd/"><img src="http://new.msn.golfbox.cn/wd/dbtt.jpg" style="width:220px;height:160px"/></a>’
+ ‘<input type=hidden name="sogouAccountId" value="202014">’
+ ‘</div>’
function iclose()
{
document.getElementById(‘sogoubox’).style.display=’none’;
}
lighttpd试用
$HTTP["host"] =~ "^www.(.*)" {
url.redirect = ( "^/(.*)" => "http://%1/$1" )
}
# %0 => domain name + tld(全域名)
# %1 => tld(顶级域名,见上例)
# %2 => domain name without tld(全主机名)
# %3 => subdomain 1 name(一级域名)
# %4 => subdomain 2 name(二级域名)
url_rewrite_concurrency
use strict;
$|=1;
{
my ($id,$url,$client,$ident,$method) = ( );
($id, $url, $client, $ident, $method) = split;
{
my ($domain,$option) = ($1,$2);
print “$id $domainn”;
}
else
{
print “$idn”;
}
}
url_rewrite_access deny !rewriteurl
url_rewrite_program /home/squid/etc/redirect.pl
#url_rewrite_children 10
url_rewrite_concurrency 10
squid源站故障转向(终结篇)
总结这批客户的跳转要求,其实格式都比较统一,大抵就是*.abc.com(.cn)坏了就转到abc.cdn.21vokglb.cn。在3月24日的博文最后,已经有了一个思路——既然无法执行php的header(Location)和strstr(%U),那么就干脆在squid的src里对%U进行操作好了。
squid-src/errorpage.c中关于%U的注释是:
U – URL without password
相关语句是:
p = r ? urlCanonicalClean(r) : err->url ? err->url : “[no URL]”;
只要把url按”.”分割,然后取出第二个域abc,就可以在html代码中给它加上跳转后的url了——这一步也能在src里完成,不过以后不好修改了,虽然现在这样子的定制性也强不到哪去~
从大二到现在无数年了,c已经属于忘到冥王星外的东东,于是一个一个的翻c的字符串函数,从strstr、strchr、strcat、strtok、strsep到最后终于发现sscanf。只要在src/errorpage.c的588行下加这么一句话就可以了:
587 case ‘U’:
588 p = r ? urlCanonicalClean(r) : err->url ? err->url : “[no URL]”;
589+ sscanf(p,”%*[^.].%[^.]”,p);
590 break;
然后编译安装,一路通过没有问题~~启动squid服务,测试一下%U吧~
先把ERR_ACCESS_DENIED内容修改如下:
<HTML>
<BODY>
<head>
<META HTTP-EQUIV=”refresh” Content=”0;URL=http://%U.cdn.21vokglb.cn/index.htm”>
</head>
</HTML>
然后在squid.conf中增加对自己本机的访问控制如下:
acl test src 222.62.104.189/255.255.255.255
http_access deny rao
squid -k reconfigure生效,访问一下www.xyfunds.com.cn,果然跳转到xyfunds.cdn.21vokglb.cn/index.htm啦~~
完毕。
虽然最终还是没能达到任意定义跳转url的目标,不过就本身的出发点来说,还是完成了需求。这也是我N年来第一次重新看C,也是第一次修改squid代码,虽然只加了一句~~~不过意义还是有滴,晚上吃个鸡蛋自我犒劳一下咯。
squid和nginx的error_page差别
一是直接error_page 502 503 504 = http://xyfunds.cdn.21vokglb.cn/index.htm;
二是error_page 502 503 504 =200 @fetch;然后location ~* @fetch {……}。
网上看到很多自定义error_page的方式,比如error_page 404 /404.php;把变量都传给php去分析处理;
也可以在location里用if(){}做rewrite等等。
squid的error_page,可以有error_directory、error_map、deny_info三种方式。其中deny_info仅适用于ERR_ACCESS_DENIED一种情况;目前对于源站故障跳转,采用的是修改error_directory里html的meta;今天由nginx的方式想到采用error_map试试,于是写了如下php页面:
<?php
switch ($_SERVER[‘SERVER_NAME’])
{
case ‘www.xyfunds.com.cn’:
header(“Location: http://xyfunds.cdn.21vokglb.cn/index.htm”);
break;
default:
echo $_SERVER[‘SERVER_NAME’];
}
?>
但测试结果,ERR的页面却是一片空白……在squid.conf,default中看到,原来error_map只是返回定义页面的内容,header还是原先的。
于是又想针对squid返回的%U,进行strtr(),然后再进行meta,如下:
<?php
$urlarray = array(‘com.cn’=>’21vokglb.cn’);
$urlrewrite = strtr(“%U”,$urlarray);
echo <META HTTP-EQUIV=”refresh” CONTENT=”0; $urlrewrite”>;
?>
很可惜,测试结果是把这串字符直接显示在了页面上。
这两个结果让我很无语。如果说squid不支持php,那为什么它能识别出来上一个是修改header而不是页面内容所以不显示文本;如果说squid支持php,那为什么下一个又无法执行呢?
想对%U进行操作,难道非得到squid/src/errorpage.c里去修改么?C语言的字符串处理没有封装好的函数,真的好麻烦的说……