令我发狂IE cookie的问题

这个问题基本上可以令人发狂。在 6.0 sp3 091208-2036以后的版本(其他版本也可能有问题)。
如果使用php的set_cookie 如果设置的过期时间比较短,比如180秒后过期,那么cookie将无法创建。
而之前的IE版本以及如Firefox,Chrome,Safari等均没有这个现象。

发现这个原因,是我折腾了一晚上,并且特意弄了台windows,用QQ远程在一个网友的机器上反复折腾,
当临近崩溃的最后一秒发现的。

血泪的教训。 cookie的过期时间最好在1个小时以上,通过将过期时间写入值存入cookie,然后判断,不要依赖浏览器,尤其是
狗屎的IE6.

听说国外有人给IE6举办了葬礼,什么时候国内能有呢,那对于web开发真是个福音。不过,IE7/8对于网银的支持实在
是糟糕透顶,对于我这种mac用户,IE乃至windows的唯一用途就是上网银。。。所以,我的虚拟机用的还是IE6.。。
天大的讽刺!

还好,招商银行支持iphone,方便多了。

PHP:call_user_func_array 导致的诡异事件

最近,在升级原创榜时,发现了Doggy中Dt模版的一个诡异现象。当访问某些页面时,不定期出现Gateway timeout.
PHP没有报任何错误,也没有任何错误日志或者信息输出。我一度认为是模版的Tag有问题,因为当尝试
删除某些Tag后会解决这个问题。好景不长,更多的页面也冒出此类错误,然而当我把模版内部解析缓存关闭后,则不会出现此类错误。 于是转头跟踪Dt模版的内部调用。Dt模版的设计中,当模版第一次被解析后,将模版转换成Doggy_Dt_NodeList对象,并将它缓存到内部cache中。当模版下次被调用时,如果模版没有变动,则直接调用NodeList的render方法,这样可以减少模版解析的时间。然后,当从缓存中被反序列的Doggy_Dt_NodeList,却无法正确执行filter。看下面的一段代码:

if (isset(Doggy_Dt::$filters[$name])) {

foreach ($args as $i => $argument) {
# name args
if (is_array($argument)) {
foreach ($argument as $n => $arg) {
$args[$i][$n] = $this->resolve($arg);
}
}
else {
# resolve argument values
$args[$i] = $this->resolve($argument);
}
}
array_unshift($args, $object);
$object = call_user_func_array(Doggy_Dt::$filters[$name], $args);
}

罪魁祸首是:call_user_func_array(Doggy_Dt::$filters[$name], $args)。

此处调用的call_user_func_array导致无法autoload class。于是php就悄无声息的挂了。

说是诡异,就是因为,如果不是反序列出来的,则能够正常的调用。
此外,在官方手册中,对于call_user_func_array还有一段注释:

Note: Callbacks registered with functions such as call_user_func() and call_user_func_array() will not be called if there is an uncaught exception thrown in a previous callback.

也就是说,如果之前call_user_func_array()导致了未知的异常,那么后续的call_user_func_array则不会被调用。

后来检查,的确模版中也有错误的filter tag。 但,奇怪的是只有是反序列(unserialize)回来的才会重复此类
现象。 因此,未必是完全如注释所说的原因。
我猜测,有可能是call_user_func_array的错误导致了反序列后的对象无法正确再次调用call_user_func_array,至少在call_user_func_array中无法正确autoload class。

解决方法,其实也是修复上面代码的一个bug,在call_user_func_array前增加is_callable判断。
if (isset(Doggy_Dt::$filters[$name]) && is_callable(Doggy_Dt::$filters[$name])) {

}
else {
Doggy_Log_Helper::warn(’unkown filter:’.$name);
}

于是,世界就太平了。

MongoDB practice:基于MongoDB的好友消息动态的实现思路(How to build activity-streaming with MongoDb)

好友动态是SNS最常见的功能。在设计“视觉中国原创榜”的好友动态时,也遇到如何实现的问题。和普通的SNS不同,
视觉中国原创榜用户不仅仅关注好友的动态,而且也要关注自己的作品和自己曾经关注过的作品的动态。
这样,就需要给用户分别push 3种不同的动态: 我的作品的动态,我关注过的作品(包括收藏过,评论过,评分过)的动态,
以及我关注的人(followed)的动态,未来还有好友的动态。这些动态,用户都应该可以取消关注。如何实现?
使用传统的数据库会面临很多问题,比如如何sharding。 幸运的是,我们用的MongoDB,这给我们解决问题带来了极大的方便。

首先,对每个用户,分别设计以下 collection

//by nightsailer.com
activity_streaming.feed, 属性分别为:
$schema = array(
_id, 用户id
followed_art => array(), 用户关注的作品id列表
followed_people=>array(), 用户关注的人id列表
my_art => array() 用户的作品id 列表
)
//by nightsailer.com
activity_stream.user:
_id
type=> 动态steam的类型(我关注的作品,我的作品,我关注的人)
stream_target => 对应动态stream的对象(作品id,人id)
stream => array() FIFO数组,存放activity的DBRef
time => int 最后一次activity的时间戳

activity_stream.site
_id uuid
type: 动态类型
data: hash 动态数据
time

activity_stream.queue
同 activity_stream.site
存放待处理的动态队列

我们使用异步处理方式,按照以下流程:
1. 当用户某些动作产生一个事件后,将事件push到activity_stream.queue, 通知worker进行处理
2. worker 被唤醒,从activity_stream.queue中提取未处理的事件。
3. worker 将事件放入activity_stream.site,作为全站动态广播
4. worker 从activity_streaming.feed中反向查找事件的作品或作者是否有对应的人,如果有,则将此事件id
push到activity_stream.user 的对应fifo数组中。
(这是MongoDB最兴奋的地方,可以直接查询数组中的值,只要对数组进行索引)

这样,用户可以:

- 从activity_stream.user 中删除某个事件
- 从activity_stream.feed 中删除某个关注的对象(实现类似忽略这个人的动态,忽略这个作品的动态等等)
- 当用户关注好友后,将其加入activity_stream.feed
- 当用户上传作品后,将作品加入activity_stream.feed
- 当用收藏、评分、评论后,将其作品加入activity_stream.feed

以上是MongoDB实践的第二篇,待续。

MongoDB practice: My Perl GridFS Wrapper

简单写了一个Perl版本的GriFS的wrapper:

package CZone::GridFS;
use strict;
use MongoDB::GridFS;
use Path::Class;
use Digest::file qw(digest_file_hex);
use Digest::MD5 qw(md5_hex);
use IO::File;
use Data::Dumper;
use Any::Moose;

has database => (
isa => ‘MongoDB::Database’,
is => ‘ro’,
required => 1
);

has _gridfs => (
isa => ‘MongoDB::GridFS’,
is => ‘ro’,
lazy => 1,
builder => ‘_build__gridfs’,
);

has _file_collection => (
isa => ‘MongoDB::Collection’,
is => ‘ro’,
lazy => 1,
builder => ‘_build__file_collection’
);

sub _build__gridfs {
my $self = shift;
return $self->database->get_gridfs;
}

sub _build__file_collection {
my $self = shift;
return $self->database->get_collection(’fs.files’);
}

sub get_bytes {
my ($self, $id ) = @_;
my $file = $self->_gridfs->find_one({_id => $id });
my $bytes;
my $fh = new IO::File \$bytes,’>';
$file->print($fh);
return $bytes;
}

sub store_file {
my ($self, $file_path) = @_;
my $file = file($file_path)->absolute;
return undef unless -e $file;
my $md5 = digest_file_hex($file,’MD5′);
my $fh = $file->open(’r') or return undef;
return $self->_store_fh($fh,$md5);
}

sub _store_fh {
my ($self,$fh,$md5) = @_;
# $grid_file isa MongoDB::GridFS::File
my $grid_file = $self->_gridfs->find_one({ ‘md5′ => $md5});
if ($grid_file) {
$self->_inc_refs($grid_file->info->{_id});
return $grid_file->info->{_id};
}
else {
my $oid = $self->_gridfs->insert($fh,{
refs => 1,
md5 => $md5,
});
return $oid;
}
}

sub store_bytes {
my ($self, $bytes) = @_;
my $md5 = md5_hex($bytes);
my $fh = new IO::File \$bytes,’<';
# my $fh = FileHandle->new;
# $fh->open(\$bytes,’<');
return $self->_store_fh($fh,$md5);
}

sub unlink {
my ($self, $id ) = @_;
$self->_dec_refs(MongoDB::OID->new(value =>”$id”));
}

sub _inc_refs {
my ($self,$id) = @_;
$self->_file_collection->update({_id => $id },{ ‘$inc’ => { refs => 1}});

}

sub _dec_refs {
my ($self,$id) = @_;
$self->_file_collection->update({_id => $id },{ ‘$inc’ => { refs => -1}});
}

sub gc {
my $self = shift;
$self->_gridfs->remove({refs => 0});
}

no Any::Moose;
__PACKAGE__->meta->make_immutable;
1;

__END__

这是从czone项目中的PHP代码移植过来的。
方便将gridfs中的文件读写到scalar中。同时,通过检查存储文件的md5值,并记录相同文件的引用计数,相同文件只存储一个copy,节省空间。(BSON格式对于空间的需求是非常大的)

I’m back.

域名转移完毕. 现在是新域名 nightsailer.com了.

wordpress中需要更新settings中的WordPress address (URL)和Blog URL.
然后使用301将night9.cn 重新定向到 nightsailer.com.

重新启用nightsailer.com,转移中

一直比较纠结要不要转移到godaddy中, 今天看到张宴的godaddy域名转移过程, 痛下决心
重新启用我原来的域名 nightsailer.com

这是我99年-2000年左右使用的个人域名,万网注册的,
当时用的mt3架的blog. 后来工作忙,不知道过期(可见万网的服务),当时连要自己设置ns记录都要收费!
索性就放弃了.

早上找了$7.49 的优惠码,注册2年,用支付宝付款,全部下来105.04RMB, 比起国内,真的很便宜了.

手里的几个cn域名马上也过期了,再也不续费了, .com的域名也准备转移走.

Notes: CentOS5.4编译Perl IO::Tty的处理

直接编译IO::Tty,会导致:

Can’t load ‘/root/.cpan/build/IO-Tty-1.08-PWZkbn/blib/arch/auto/IO/Tty/Tty.so’ for module IO::Tty:
undefined symbol: strlcpy at …..

快速修正, 打开Makefile
DEFINE = -DHAVE_DEV_PTMX -DHAVE_GETPT -DHAVE_GRANTPT -DHAVE_OPENPTY -DHAVE_POSIX_OPENPT -DHAVE_PTSNAME -DHAVE_PTSNAME_R -DHAVE_PTY_H -DHAV
E_SIGACTION -DHAVE_STRLCPY -DHAVE_SYS_STROPTS_H -DHAVE_TERMIOS_H -DHAVE_TERMIO_H -DHAVE_TTYNAME -DHAVE_UNLOCKPT -DHAVE__GETPTY

去掉 中间的-DHAVE_STRLCPY和最后的-DHAVE__GETPTY

重新编译, make test.

ok!

PHP编写命令行脚本和后台运行程序的注意事项

在一些场合(如开发,测试), 可能需要使用PHP编写一些命令行的处理脚本,或者是长时间
后台运行的任务, 需要注意以下准则:

准则1. 尽量避免使用PHP编写后台运行程序, 尤其是类似while(true){….} 这种循环的处理脚本.

比如,有时候我们需要定期检查数据库,然后有数据进行处理,没有数据等待.
我强烈建议不要使用PHP编写这样的类似service的脚本. php的gc并不稳定, 当运行到一定时候,
会异常退出. 另外, PHP也不擅长做命令行脚本. 看看Phing, 号称PHP port的Ant, 但是性能
糟糕的一塌糊涂, 远不如使用几个unix工具+shell来的快捷. (更别提windows,那基本是不可用).

使用Perl,Python来完成相应的任务吧, 那会让你很愉快, 或者,Ruby也不错.

准则2 编译特殊版本的php
如果一定要沉迷于PHP解决, 那么首先, 重新编译PHP,去掉所有不实用的模块. 增加PHP的稳定性.

准则3 不要直接使用fork 或者 nohup
不要在PHP中直接使用fork来进入后台运行, 也不要直接使用nohup来运行直接运行php 循环脚本.

准则4 使用 shell guard 来完成PHP的后台循环运行
php的脚本只需要处理一次数据后马上退出,不要使用任何while(true) 这样的脚本来挂起等待.
相反的, 可以使用以下的shell gurad 来完成上述工作:
night9.cn# cat guard.sh
DIR=`pwd`
while true; do
echo “start php script ..”
php $DIR/thumbnail_worker.php
echo “respawn the worker…”

上面这种脚本我称之为shell guard.这样的好处是你的后台脚本永远可以可靠的运行,一旦因为php自身出现问题(如内存溢出),那么马上就可以立即重新执行.
对于检查,执行,休眠模式,可以使用和以下类似的:
while true; do
php ./realtime_data_worker.php
echo “paused 10s”
sleep 10
done

上述shell guard的另一个好处就是一旦你更新了Php文件, 下次运行时就是新的了. 而无须kill掉在重启.
特别适合频繁变更的情况,减少你的工作量.

准则5 可以在你PHP脚本中监控执行的情况, 当执行了一定次数或者内存消耗到一定,则exit, 释放占用的内存,
防止内存泄露.
这条一定要和shell guard来配合.

如果你用windows, 抱歉, 这不在我考虑的范围之内.

准则6 如果以上仍然无法解决一些问题, 那么请参考第一条,使用Perl/Python重写. 立刻会药到病除 ;-)

PS: 某些copy & paste的人儿, 转载我的笔记麻烦给个出处. 我现在知道某些人为何使用我不称我,而使用类似night9.cn认为这样的第三人称说法, 都是某些热衷把转载当自己原创使用的人害得,但每次我看到这样的第三人称总会稀稀拉拉掉一地鸡皮疙瘩.

我写的所有笔记和心得都是自己实践, 主要是为自己备忘使用,都是原创,无须声明.
有时候很纳闷, 转载有必要么?
google可以告诉你一切. 减少点碳排放吧.

Mouse,Moose和MooseX::Declare

如果你是一个Perl的开发者, 现在还不知道Moose那么你对Perl的了解基本上还停留在10年前了.
虽然国内Perl的开发者寥寥无几, 但Perl的强大远远超越一般人的想象空间.

我使用Perl是让自己更愉快,因为很多事情变得很简单.

Perl的OO一般人很难理解, 但是却用了最简单和巧妙的方式实现了,想想, 一个bless搞定, 再看看
PHP之类的,多么臃肿.

有了Moose,你会发现, 不仅仅OO,AOP这些东西实现起来是多么的轻松.
当看到MooseX::Declare, 你更会惊叹, “这还是Perl么?”.

use MooseX::Declare;

class BankAccount {
has ‘balance’ => ( isa => ‘Num’, is => ‘rw’, default => 0 );

method deposit (Num $amount) {
$self->balance( $self->balance + $amount );
}

method withdraw (Num $amount) {
my $current_balance = $self->balance();
( $current_balance >= $amount )
|| confess “Account overdrawn”;
$self->balance( $current_balance - $amount );
}
}

和教条的Python相比我喜欢Perl的哲学, 同样的结果可以有不同的选择, 如何做,取决你自己.

几个很有用的TextMate bundle

TextMate的这几个bundle对我来说非常有用:

1. CTags
和vim中的类似,可以ctrl+] 可以快速列出和跳转到symbol所在的文件位置. 有点遗憾的是不能想vim那样返回.
适合多数的编程语言.

2. Devel::IntelliPerl
Perl的自动解析和语法提示,能够根据指定变量的上下文给出语法提示,主要是列出可以调用的方法. 很cool.
除了TextMate,还支持vim. 由于使用Moose进行反射分析,可以显示出继承的方法. 比单纯的正则分析
要准确的多.

3. Act
可以使用正则表达式快速搜索项目中的文件. 使用Perl编写, 是一个快速和强大的grep 替代品.
TextMate中的search in project可以休息了. 如果你用TextMate,就知道在大的项目中使用search in project是
一种煎熬. 我很多次不长记性的调用,看着高起的CPU和风扇声,被迫kill掉textmate. 使用Act可以省去find+grep
的诸多不便. 而且, 由于是Perl编写,你可以使用完整的PCRE, 而不是GNU的精简版, 甚至你可以只搜索某种
编程语言的.很不错的.

其他如project plus,git bundle这些都是标配了.

Next Page »