消除瑞星过滤邮件时给邮件添加的标题

ysm
cleverysm@gmail.com

很久以来一直在用Thunderbird的pop3功能收邮件,时间长了邮箱里积累了很多旧邮件,今天打算整理一下。由于曾经有段时间用瑞星,结果瑞星的邮件过滤功能在我很多邮件的标题里加了“(瑞星提示-此邮件可能是垃圾邮件)”这样的字样,而这邮件明明是正常的工作邮件,这个死瑞星真是笨死了。以前懒得理睬,结果时间一长,邮箱里竟有上百封邮件都被瑞星打了标签,现在发现看上去是多么的不爽,于是决定要解决这个问题。
首先,将Thunderbird里的几封被瑞星修改的邮件以eml的格式保存成文件。由于eml文件就是文本格式的,所以可以用notepad++打开看看是什么样子。其实不用太了解eml文件格式的细节也能很容易对文件各部分的含义作出判断,瑞星修改的是信件标题,也就是Subject:处的内容,只要找到瑞星修改的内容,然后删掉就ok了。
经过对照几个文件,发现瑞星就是在Subject:后面插入了一段如下的字符:
=?gb2312?B?KMjw0MfM4cq+LbTL08q8/r/JxNzKx8Csu/jTyrz+KQ==?=
虽然看不懂具体什么含义,但能很容猜出来,就是用gb2312编码的那句需要删掉的话。
由于需要修改的文件比较多,决定用python写段程序批量修改一下。
基本过程很简单,就是把有问题的邮件都保存成eml文件,放到一个目录下,然后程序依次读取文件,将要删掉的那段字符用空字符串地换一下就行了,代码如下:

def fix_eml_mail_1(from_dir, to_dir):
    from_dir_list=os.listdir(from_dir)
    for file in from_dir_list:
        from_file = open(from_dir+'/'+file)
        to_file = open(to_dir+'/'+file, 'w')
        content = from_file.read()
        to_file.write(content.replace('=?gb2312?B?KMjw0MfM4cq+LbTL08q8/r/JxNzKx8Csu/jTyrz+KQ==?=',''))
 
    print '%d files fixed' % len(from_dir_list)

修改后的eml文件再导入Thunderbird就ok了。导入的最简单办法就是,直接用鼠标拖进去。
不过,导入之后发现一个问题,仍有一小部分邮件标题里还有那句讨厌的话。再保存出来打开仔细观察,发现,其中有一部分是因为被瑞星修改标题的邮件被转发,于是标题再次被修改,这样那句话编码后的字符串就不是程序里的那样子,也就不会被删掉了。甚至有个别信件虽然不是转发或回复的信件,但是信件原有的标题不知为什么被一并包含进=?与?=之间进行了编码转换,同样无法用上面的代码消除。那该怎么办呢?看来得想办法把编码后的标题解码成正常文字,这个功能自然就想到了用python的email包。
首先,我们需要导入email包:

import email

然后,将文件读入一个Message对象msg中:

from_file = open('somewhere/mail.eml')
msg = email.message_from_file(from_file)

接着读取邮件中的subject内容:

subject = msg.get("subject")

这时读出来的subject还是经过编码后的字符串,需要对其进行解码:

dh = email.Header.decode_header(subject)

这里的dh是一个包含多个元组元素的列表[('',''),('','')],列表中每个元素是一个元组(‘@#%%^%$#’,'charset’),元组的第一个元素是剔除了编码信息的subject内容,这个内容到这里依然是经过编码的,而其所使用的编码就是元组的第二个元素。元组可以有多于一个,表示subject里有多个编码的字段,比如可以有如下一个subject:
=?gb2312?B?asdfdgdsfdss?= =?gb2312?B?##@$%^&%$#?=
两段要有空格或换行隔开,这样dh中就是有两个元组了。
在处理问题邮件过程中发现瑞星在修改subject时是直接在原subject内容前面不加间隔紧贴着插上那么一句话,如果在这里不加处理直接用decode_header会无法正确识别瑞星那句话和原有的subject,唉,真可恶。只好在decode_header之前另加一个处理,上一句就变成:

subject = subject.replace('?=','?= ')
dh = email.Header.decode_header(subject)

现在,我们就可以用下面一段来把subject打出来看看是什么效果:

for i in range(len(dh)):
            encode = dh[i][1]
            if encode is None:
                encode = msg.get_content_charset()
                if encode is None:
                    encode = 'gbk'
            print "subject:", dh[i][0].decode(encode)

这个地方的编码首先是根据subject中编码信息来进行解码,但subject也不一定会是编码后的文本,这时dh[i][1]返回的是None,那么就试着查一下content编码,如果也不差不到,就用gbk,当然gbk也不一定就保险,因为也许有utf-8的信件,但一般我们多数情况下还是用gb2312和gbk写中文的,真碰到问题改一下就行了。
好,接下来就是需要把那段可恶的话删掉,然后给msg重做一个subject:

        new_subject = ''
        new_dh=[]
        for i in range(len(dh)):
            encode = dh[i][1]
            if encode is None:
                encode = msg.get_content_charset()
                if encode is None:
                    encode = 'gbk'
 
            str = dh[i][0].decode(encode)
            if str is not None:
                new_subject = str.replace(u'(瑞星提示-此邮件可能是垃圾邮件)','')
                new_subject = new_subject.encode(encode)
                new_dh.append((new_subject, encode))
 
        new_header = email.Header.make_header(new_dh)
        msg.replace_header('Subject', new_header)
 
        to_file.write(msg.as_string())

把修改后的eml文件再导回Thunderbird,终于,干净了。
拜拜,瑞星。

完整的两段函数如下:

def fix_eml_mail_1(from_dir, to_dir):
    from_dir_list=os.listdir(from_dir)
    for file in from_dir_list:
        from_file = open(from_dir+'/'+file)
        to_file = open(to_dir+'/'+file, 'w')
        content = from_file.read()
        to_file.write(content.replace('=?gb2312?B?KMjw0MfM4cq+LbTL08q8/r/JxNzKx8Csu/jTyrz+KQ==?=',''))
 
    print '%d files fixed' % len(from_dir_list)
 
    pass
def fix_eml_mail_2(from_dir, to_dir):
    from_dir_list=os.listdir(from_dir)
    for file in from_dir_list:
        from_file = open(from_dir+'/'+file)
        to_file = open(to_dir+'/'+file, 'w')
        msg = email.message_from_file(from_file)
        subject = msg.get("Subject")
 
        subject = subject.replace('?=','?= ')
        dh = email.Header.decode_header(subject)
        new_subject = ''
        new_dh=[]
        for i in range(len(dh)):
            encode = dh[i][1]
            if encode is None:
                encode = msg.get_content_charset()
                if encode is None:
                    encode = 'gbk'
 
            str = dh[i][0].decode(encode)
            if str is not None:
                new_subject = str.replace(u'(瑞星提示-此邮件可能是垃圾邮件)','')
                new_subject = new_subject.encode(encode)
                new_dh.append((new_subject, encode))
 
        new_header = email.Header.make_header(new_dh)
        msg.replace_header('Subject', new_header)
 
        to_file.write(msg.as_string())

代码下载:risingmail.py

Ubuntu下环境变量该写进哪个文件里

(以Ubuntu10.04,bash shell为准)
在linux下设定环境变量时,如果只是临时用一下,可以直接在shell下用set或export命令设定环境变量,如果希望此环境变量每次开机或打开shell时自动设定而无须每次都手动设定,那么需要将export命令写入某个系统文件中,拥有这种功能的文件常见的有如下几个:
/etc/environment 或 /etc/profile 或 ~/.profile 或 /etc/bash.bashrc 或 ~/.bashrc等。
有这么多可以用,到底它们有什么区别,谁先谁后呢?

首先,来看看这几个文件都是干什么的:
1./etc/environment–>是系统在登录时读取的第一个文件,用于为所有进程设置环境变量。系统使用此文件时并不是执行此文件中的命令,而是根据KEY=VALUE模式的代码,对KEY赋值以VALUE,因此文件中如果要定义PATH环境变量,只需加入一行形如PATH=$PATH:/xxx/bin的代码即可。
2./etc/profile–>是系统登录时执行的第二个文件,可以用于设定针对全系统所有用户的环境变量。
3.~/.profile–>是对应当前登录用户的profile文件,用于定制当前用户的个人工作环境。
4./etc/bash.bashrc–>是针对所有用户的bash初始化文件,在此中设定的环境变量将应用于所有用户的shell中,此文件会在用户每次打开shell时执行一次。
5.~/.bashrc–>是对应当前登录用户的bash初始化文件,当用户每次打开shell时,系统都会执行此文件一次。

那么根据以上描述,这几个文件的执行先后顺序应当是:
/etc/enviroment –>/etc/profile –>~/.profile –>/etc/bash.bashrc–> ~/.bashrc
为了验证此顺序是否正确,这里可以做一个小试验,假定我们登录的用户名为xyz。在/etc/environment中加入一行:
ENV_MSG=”this is /etc/environment”
这样也就是添加了一个环境变量ENV_MSG,然后在/etc/profile中加入两行代码:
echo $ENV_MSG >> /home/xyz/log.txt
echo “this is /etc/profile” >> /home/xyz/log.txt
这样,如果/etc/environment在profile之前被系统读取,则在/home/xyz/log.txt 中会先后打印出EVN_MSG的值和 this is /etc/profile这两条消息。
在/home/xyz/.profile中加入一行代码:
echo “this is .profile” >> /home/xyz/log.txt
在/etc/bash.bashrc中加入一行代码:
echo “this is /etc/bash.bashrc” >> /home/xyz/log.txt
在/home/xyz/.bashrc中加入一行代码:
echo “this is .bashrc” >> /home/xyz/log.txt
然后,重启计算机,看看log.txt文件中会是什么样子。
启动计算机后以xyz用户登录并立即打开/home/xyz/log.txt,可以看到文件中有如下三行消息:
this is /etc/environment
this is /etc/profile
this is .profile
这说明系统在启动登录的过程中依次读取执行了/etc/enviroment 、/etc/profile和~/.profile中的内容。
然后打开一个shell窗口,log文件中就会增加两行消息:
this is /etc/bash.bashrc
this is .bashrc
这说明在打开shell过程中,系统又依次执行/etc/bash.bashrc和~/.bashrc。如果关闭shell窗口后再次打开一个新的shell窗口,则log文件中会又增加两行同样的消息。由此可以获知,每次打开一个新shell,系统都会重复执行这两个文件,而不会再动那头三个文件的内容。
接下来我们再打开/etc/environment ,把刚才写入的那行改成ENV_MSG=”this is not /etc/environment”,然后注销,重新以xyz登录,结果会发现log文件中会又多了三行:
this is not /etc/environment
this is /etc/profile
this is .profile
这也就看出来,注销重登录也会引发系统对这三个文件的读取与执行。
不过,如果按下Ctrl+Alt+F1,然后登录xyz,那么log文件中会多出来如下几行,这又是怎么回事呢?
this is /etc/bash.bashrc
this is /etc/environment
this is /etc/profile
this is .bashrc
this is .profile

ubuntu的init与系统服务设置

init

Init是位于/sbin/init的一个程序,它是在linux下,在系统启动过程中,初始化所有的设备驱动程序和数据结构等之后,由内核启动的一个用户级程序,并由此init程序进而完成系统的启动过程。

ubuntu与传统的linux略有不同,使用upstart完成系统的启动,但表面上仍维持init程序的形式。

运行级别

传统上,linux有几种不同的运行级别,包括如下几种:

# 0 - 停机
# 1 - 单用户模式
# 2 - 多用户,没有 NFS
# 3 - 完全多用户模式(标准的运行级)
# 4 – 系统保留的
# 5 – X11 (x window)
# 6 - 重新启动

系统启动后处于哪一种级别由init读取/etc/inittab文件中的缺省级别设置来确定,一半图形界面的系统是进入级别3。

但是ubuntu与传统的不太一样,默认情况下是找不到/etc/inittab文件的,而且运行级别也有差别,具体分这样几个级别:

# 0 – 关闭系统

# 1 – 单用户模式

# 2~5 – 完整的多用户模式

# 6 – 重新启动

也就是说,默认情况下级别2、3、4、5都是一样的,同时系统的默认级别设定也不是在inittab文件中,而是写在/etc/init/rc-sysinit.conf文件中。打开此文件,可以找到下面一句:

env DEFAULT_RUNLEVEL=2

这表明系统当前默认是进入级别2。

另外,在此文见中还有一段以if [ -r /etc/inittab ] 开始的代码,这里保留了使用inittab指定系统默认运行级别的功能,也就是说,如果用户手动创建了/etc/inittab,那么init将以/etc/inittab中指定的默认运行级别进行系统的启动。比如说用户希望系统以级别3为默认运行级别,则只需在inittab文件中加入如下一行:

id:3:initdefault:

在经过/etc/init/rc-sysinit.conf确定运行级别后,init将进一步运行/etc/init.d/rc,然后根据级别进入/etc/rc[?].d启动或关闭相应的服务。

服务的启动与关闭脚本

ubuntu下启动与关闭服务的脚本存放与/etc/rc[?].d目录下。其中[x]表示0~6,分别对应级别0~6,如/etc目录下的rc0.d  rc1.d  rc2.d  rc3.d  rc4.d  rc5.d  rc6.d。假设rc-sysinit.conf或inittab中指定的默认级别是2,那么init将执行/etc/rc2.d目录下的脚本启动或关闭相应服务。

如果打开/etc/rc[?].d目录,会发现这些目录下的文件都是形如Snnxxxx或Knnxxxx的符号链接,而且都是指向/etc/init.d。也就是说不同运行级别下服务的启动或关闭脚本均是放在/etc/init.d下,只不过根据不同级别的需要,在对应/etc/rc[?].d目录下放一个链接,不同的级别会需要不同的服务,因此不同/etc/rc[?].d目录下的链接文件也不尽相同以此区分。

其中链接文件中以S开头的表示在调用/etc/init.d目录中对应脚本的时候会传递一个start参数,也就是启动对应服务,而以K开头的则是传递一个stop参数,由此关闭此服务,此处的K表示kill。

S和K后面的nn是一个数字,表示本脚本被执行的先后顺序,小号在前大号在后,这样以解决不同服务之间可能存在的先后依赖关系。比如说ftp服务依赖于网络服务的启动,所以ftp服务的编号就要大于网络服务的编号,在网络服务启动后再行启动。

最后的xxxx则是服务的名字。

另外,除了/etc/rc[0~6].d文件外,还有一个/etc/rcS.d目录,这个目录下的服务脚本与/etc/rc[0~6].d格式类似,也为指向/etc/init.d中的脚本的链接,但是会在/etc/rc[0~6].d中的脚本执行前首先被执行。

服务的安装

根据上面的介绍,如何将一个软件安装为服务也就比较清楚了,那就是在/etc/init.d添加一个服务的启动脚本,然后在需要启动服务的对应级别的/etc/rc[0~6].d中按照文件名格式添加一个指向/etc/init.d中脚本的符号链接。

以apache2为例,默认情况下,apache2编译安装在/usr/local/apache2,apache2的服务器启动脚本是/usr/local/apache2/bin/apachectl,那么安装服务就是要把此apachectl拷贝到需要启动apache2服务器的运行级别对应的/etc/rc[?].d目录下,一半来说ubuntu是运行在级别2下,所以也就是拷到/etc/rc2.d下,命令如下:

sudo cp /usr/local/apache2/bin/apachectl /etc/init.d/httpd

在这里,我们把拷贝的脚本文件名改为惯用的httpd,那么在系统服务中apache2的名称也就是httpd。

手动添加服务的话,就是要在/etc/rc2.d里为httpd作一个形如Snnxxxx的链接。假定启动顺序我们定为80,那么就是要建立一个指向/etc/init.d/httpd的链接/etc/rc2.d/S80httpd,命令如下:

sudo ln -s /etc/init.d/httpd /etc/rc2.d/S80httpd

然后用sysv-rc-conf或者chkconfig –list检查一下就能看到已经在运行级别2下建立的名为httpd的服务,重启后,系统会自动启动apache2。

现在要手动启动、关闭或重启httpd服务器可以使用service命令+服务名+参数的形式,如下三句分别是启动、关闭和重启apache2服务器的命令:

service httpd start

service httpd stop

service httpd restart

要安装服务除了以上手动作链接外,还可以使用一些工具软件来实现,比如常用的有update-rc.d、chkconfig和sysv-rc-conf等。

这里主要以update-rc.d为例

update-rc.d

如用update-rc.d在运行级别2,3,4,5中都添加httpd服务启动命令,并指定启动序号是80,可以如下(注意命令最后的那个点号不能少):

sudo update-rc.d httpd start 80 2 3 4 5 .

如果要在运行级别2,3,4,5中都添加httpd服务关闭命令,并指定关闭序号是80,则可以如下(注意命令最后的那个点号不能少):

sudo update-rc.d httpd stop 80 2 3 4 5 .

而如果要删除httpd服务,则用以下命令就删掉/etc/rc[?].d中所有存在的指向/etc/init.d/httpd的链接:

sudo update-rc.d httpd remove

另外,还可以用带有defaults参数的形式同时向运行级别2,3,4,5中添加启动服务命令,即Start命令,并同时向运行级别0,1,6添加关闭命令,即Kill命令,其中start命令的序号是80,kill命令的序号是90:

sudo update-rc.d httpd defaults 80 90

这条命令也等同于(注意命令stop前面和最后的那两个点号不能少):

sudo update-rc.d httpd start 80 2 3 4 5 . stop 90 0 1 6 .

以上两条命令的效果就是作了如下几个链接:

/etc/rc0.d/K90httpd -> ../init.d/httpd

/etc/rc1.d/K90httpd -> ../init.d/httpd

/etc/rc6.d/K90httpd -> ../init.d/httpd

/etc/rc2.d/S80httpd -> ../init.d/httpd

/etc/rc3.d/S80httpd -> ../init.d/httpd

/etc/rc4.d/S80httpd -> ../init.d/httpd

/etc/rc5.d/S80httpd -> ../init.d/httpd

chkconfig

查看所有服务在所有级别下的情况:

chkconfig –list

查看某服务的情况,如下将查看httpd服务在哪些级别下是被启动的:

chkconfig httpd

在此,如果httpd在2,3,4,5下是被启动,将返回信息:

httpd 2345

sysv-rc-conf

这个软件是有图形界面的,比较直观简单,就不多说了,看看就明白了。

rc.local

在/etc/rc[2~5].d目录下都会有一个S99rc.local,这是一个指向/etc/init.d/rc.local的链接,可以看出,在正常的2~5级别启动的最后都会调用这个rc.local脚本,而/etc/init.d/rc.local中又会检查是否存在/etc/rc.local,并运行之,因此,我们就可以在/etc/rc.local中写入代码,来随系统启动某些程序,实现类似服务的功能。

系统的启动过程

综上,可以看到,系统的启动调用过程就如下过程:

内核 → /etc/init/rc-sysinit.conf → [/etc/inittab] → /etc/init.d/rc → /etc/rc[?].d → /etc/init.d/rc.local → /etc/rc.local

其他linux系统

在其他系统下以上的文件结构和过程略有不同,以Redhat系的CentOS5为例,系统中默认init是使用/etc/inittab文件的,然后读取/etc/rc.sysinit,再根据运行级别进入/etc/rc[?].d。

其中,/etc/rc.sysinit是指向/etc/rc.d/rc.sysinit的链接,/etc/rc[?].d是指向/etc/rc.d/rc[?].d的链接,/etc/rc.local是指向/etc/rc.d/rc.local的链接,所以系统启动的顺序就变成如下:

内核 → /etc/inittab → /etc/ rc.sysinit(/etc/rc.d/rc.sysinit) → /etc/rc[?].d(/etc/rc.d/rc[?].d) → /etc/rc.local(/etc/rc.d/rc.local)

在线制作瓶盖头四字头像

闲来无事,用web.py与pil写了一个在线的瓶盖头四字头像制作页面,放在刚买的vps上,今天正式上线,功能比较简单,以后慢慢填其他功能。
http://webpy.beatplane.net/i

Python中文编码问题

烤鱼片
iysm.net
cleverysm@gmail.com

中文编码问题是用中文的程序员经常头大的问题,在python下也是如此,那么应该怎么理解和解决python的编码问题呢?

我们要知道python内部使用的是unicode编码,而外部却要面对千奇百怪的各种编码,比如作为中国程序经常要面对的gbk,gb2312,utf8等,那这些编码是怎么转换成内部的unicode呢?

首先我们先看一下源代码文件中使用字符串的情况。源代码文件作为文本文件就必然是以某种编码形式存储代码的,python默认会认为源代码文件是asci编码,比如说代码中有一个变量赋值:

s1=’a’
print s1

python认为这个’a'就是一个asci编码的字符。在仅仅使用英文字符的情况下一切正常,但是如果用了中文,比如:

s1=’哈’
print s1

这个代码文件被执行时就会出错,就是编码出了问题。python默认将代码文件内容当作asci编码处理,但asci编码中不存在中文,因此抛出异常。

解决问题之道就是要让python知道文件中使用的是什么编码形式,对于中文,可以用的常见编码有utf-8,gbk和gb2312等。只需在代码文件的最前端添加如下:

# -*- coding: utf-8 -*-

这就是告知python我这个文件里的文本是用utf-8编码的,这样,python就会依照utf-8的编码形式解读其中的字符,然后转换成unicode编码内部处理使用。

不过,如果你在Windows控制台下运行此代码的话,虽然程序是执行了,但屏幕上打印出的却不是哈字。这是由于python编码与控制台编码的不一致造成的。Windows下控制台中的编码使用的

是gbk,而在代码中使用的utf-8,python按照utf-8编码打印到gbk编码的控制台下自然就会不一致而不能打印出正确的汉字。

解决办法一个是将源代码的编码也改成gbk,也就是代码第一行改成:

# -*- coding: gbk -*-

另一种方法是保持源码文件的utf-8不变,而是在’哈’前面加个u字,也就是:

s1=u’哈’
print s1

这样就可以正确打印出’哈’字了。

这里的这个u表示将后面跟的字符串以unicode格式存储。python会根据代码第一行标称的utf-8编码识别代码中的汉字’哈’,然后转换成unicode对象。如果我们用type查看一下’哈’的数据类

型type(‘哈’),会得到<type ‘str’>,而type(u’哈’),则会得到<type ‘unicode’>,也就是在字符前面加u就表明这是一个unicode对象,这个字会以unicode格式存在于内存中,而如果不加u

,表明这仅仅是一个使用某种编码的字符串,编码格式取决于python对源码文件编码的识别,这里就是utf-8。

Python在向控制台输出unicode对象的时候会自动根据输出环境的编码进行转换,但如果输出的不是unicode对象而是普通字符串,则会直接按照字符串的编码输出字符串,从而出现上面的现

象。

使用unicode对象的话,除了这样使用u标记,还可以使用unicode类以及字符串的encode和decode方法。

unicode类的构造函数接受一个字符串参数和一个编码参数,将字符串封装为一个unicode,比如在这里,由于我们用的是utf-8编码,所以unicode中的编码参数使用’utf-8′将字符封装为

unicode对象,然后正确输出到控制台:

s1=unicode(‘哈’, ‘utf-8′)
print s1

另外,用decode函数也可以将一个普通字符串转换为unicode对象。很多人都搞不明白python字符串的decode和encode函数都是什么意思。这里简要说明一下。

decode是将普通字符串按照参数中的编码格式进行解析,然后生成对应的unicode对象,比如在这里我们代码用的是utf-8,那么把一个字符串转换为unicode就是如下形式:

s2=’哈’.decode(‘utf-8′)

这时,s2就是一个存储了’哈’字的unicode对象,其实就和unicode(‘哈’, ‘utf-8′)以及u’哈’是相同的。

那么encode正好就是相反的功能,是将一个unicode对象转换为参数中编码格式的普通字符,比如下面代码:

s3=unicode(‘哈’, ‘utf-8′).encode(‘utf-8′)

s3现在又变回了utf-8的’哈’。

用apache+python+wsgi_mod+web.py进行web开发

烤鱼片
iysm.net
cleverysm@gmail.com

首先是这几个软件的下载与安装,就不多说了,根据说明文档里的介绍按部就班的安装就行了。
要使用wsgi模块首先要确保将wsgi的so文件放置于apache的modules文件夹中,然后打开apache的配置文件,httpd.conf,添加如下内容:

#加载wsgi模块
LoadModule wsgi_module modules/mod_wsgi.so

#为我们的网站建立虚拟主机
NameVirtualHost *:80
<VirtualHost *:80>

    #假定我们将此网站绑定在beatplane.net域名下
    ServerName beatplane.net
    ServerAlias beatplane.net

    ServerAdmin    admin@beatplane.net
    #假定我们的网站代码是放在/var/www/webpy下
    DocumentRoot /var/www/webpy
    #配置对应目录的访问权限,这里是允许所有的访问
    <Directory /var/www/webpy/>   
        Order allow,deny
        Allow from all
    </Directory>

    #这一步是说明wgsi的处理程序,在此我们是以网站目录下的main.py为主出程序入口,所有对于beatplane.net的访问都交由main.py来处理
    WSGIScriptAlias / /var/www/webpy/main.py

</VirtualHost>

在main.py中的内容如下:

import web
import os
import sys

#注意这个地方,在wsgi环境下,相对路径的起点不是当前这个main.py的位置,而是apache的目录,所以用到相对路径的地方就需要特别处理,通过下面两行得到main.py的真实路径
curdir = os.path.dirname(__file__)
#同时要将此处加入到环境变量中,否则网站路径下的其他模块和包都无法用import引入
sys.path.insert(0, curdir)

urls = (
        ”, ‘index’,
        ‘/(.*)’, ‘index’
)

#下面两行是与普通web.py程序不同的关键处理
#注意此处变量名必须用application,而不可以是其他名称
app = web.application(urls, globals())
application = app.wsgifunc()

#以下部分是应用session的处理方法,这里就用到了上面得到的curdir,如果不如此处理而用相对路径的话,session目录可能不会如你所愿的出现在main.py所在的目录中而造成意想不到的

错误
#同时,注意创建session时传递给Session的第一个变量仍是app,而不能是上面的application,当然,如果你不需要使用session的话,下面的这一部分就不必出现在代码中
#使用session
if web.config.get(‘_session’) is None:
    session = web.session.Session(app, web.session.DiskStore(curdir+’/sessions’), {‘count’: 0})
    web.config._session = session
else:
    session = web.config._session
#sub app下使用session–>>
def session_hook():
    web.ctx.session = session   
app.add_processor(web.loadhook(session_hook))
#sub app下使用session<<–

class index:
    def GET(self, par):
        return par

#注意此处是application,而不是app
if __name__ == "__main__":
    application.run()

重启apache,然后就可以看到效果。
比如在浏览器中访问beatplane.net/hi,页面上将打印出hi。

《盗梦空间》到底有几层?

烤鱼片

cleverysm@gmail.com

翘首等待许久的《盗梦空间》终于上映了,于是前几天和死党迫不及待奔到奥纳影城一睹为快。

和大部分影迷一样,看完本片后脑子里充满了问号,对电影中一层又套一层梦境的设定也并非完全搞清楚,回来赶忙上网搜搜别人的解释,而流传最广的可能就是豆瓣上的这一个http://movie.douban.com/review/3447868/

对于文中的大部分解释,比如kick、药物作用、时间放大、limbo、各层梦主、Mal和Cobb的关系等等,我还是比较赞同的,但是对影片中梦境的分层有着不同的意见,文中认为加上现实世界一共6层,即现实世界,第一层梦境(雨中城市),第二层梦境(酒店),第三层梦境(雪山),第四层梦境(空旷城市),limbo(迷失域)。而通过观影感受以及网上拖来的枪版视频分析,我认为不是6层而是5层,其中第四层空旷城市就是limbo。

首先,从电影的一般设计规律来看到底limbo在哪里。片中有关梦境理论中最深层、最神秘、最吸引观众兴趣的莫过于这个limbo层了,而且在这次植入任务之前只有Cobb和Mal曾经进入过这里并又回到现实世界,那么一般逻辑上来说,影片必须要把最终高潮部分放在这里,给观众展现出这个limbo层的全景,并在这里解决最后的问题。显然影片的高潮最终是在空旷城市完成的,男一号Cobb也是在这里解决了他和他潜意识中一直困扰他的Mal问题。如果空旷城市是第四层,齐藤死后进入的才是limbo的话就不符合这种规律,因此这种6层设定就不太合适。

第二,在第一层梦境中,Cobb在给女孩讲述他和他老婆的故事的时候提到,“limbo变成了她的现实”,在这时候,影片明显描述的就是Cobb和Mal在那个第四层空旷城市中生活的场景。这不就是在说这个城市就是limbo吗?

第三,在第四层救Fischer时,女孩催促Cobb快走,Cobb说“齐藤现在应该已经死了,就在这里的什么地方”,那么也就是说齐藤当时就在空旷城市这一层,也就是说这里就是齐藤死后所到的limbo。

这样的话,我就认为第四层空旷城市其实就是limbo。Cobb和Mal来过,他们在这里花了50年造了这个城市,后来卧轨自杀回到现实层。Fischer在第三层被Mal杀死了,我认为是真的死了,不是像有些人说的没完全死掉,所以他也来到这里,齐藤由于在第一层中枪死掉也来到这里。但这么一来就会又出现几个问题要解释。

首先是为什么Fischer死后是到了这个由Cobb和Mal建造的城市。我的解释是,其实每个人如果要进入limbo的话,应该是进入一个独立的limbo,但是如果梦境是与他人共享的话就有可能被梦中的其他人影响的,而Fischer是被Cobb潜意识创造的Mal在第三层杀死的,所以其实Fischer就被卷入了Cobb和Mal在limbo中建造的城市,被Mal所绑架。

那么齐藤是怎么回事呢?我说了,首先,他死了,所以也在limbo,跟空旷城市是在同一层面上,但是每个人的limbo是相对独立的,他的死与其他分享梦的几个人关联度不大,因此他掉进了他自己的一个limbo,而不是像Fischer那样被Cobb影响而进入Cobb的空旷城市limbo。这两个limbo世界的关系就是同处一层,但相对独立,之间的边界就是大海。片中可以看到空旷城市和齐藤的limbo世界都在海边。借用宇宙学里关于多宇宙理论的说法,不同的limbo是在相对独立的泡泡bubble中的,bubble之间是大海,不同bubble之间又并不完全是独立的,可以穿越大海从一个bubble进入到另一个bubble,而且不同bubble中时间流逝速度可能还不一样,齐藤的就可能比较快,这也就解释了为什么齐藤会那么老,而如果按照时间顺序,齐藤应该是在Cobb和女孩进入空旷城市后才死去的,如果不同limbo时间流逝速度相同的话,Cobb应该比齐藤还老才对。

有人说Cobb是因为在空旷城市中被Mal刺了一刀死掉才进入到齐藤那里的,我不认同这种说法,因为虽然Cobb被刺了一刀,但看上去还很精神,不像要死的样子,而如果在这里死掉就会进入limbo的话,就无法解释女孩和Fischer掉下楼去为什么会回到上一层而不是也进入limbo,而且女孩打中Mal后还想给Cobb一枪,明显就是因为已经把Fischer救回去了,所以想打死Cobb让他回到上一层,但是这时Cobb阻止她这么做,因为他还要去找齐藤。所以还是那个问题,其实这里就是limbo,在limbo死掉会回到上层,这个最早Cobb和Mal通过卧轨实践过,所以女孩把Fischer踹下楼,自己跳了下去,本还想顺便给Cobb一枪一起回去的。Cobb是通过另一种方式渡海才到了年老的齐藤那里说服他杀死Cobb并且自杀一同回到现实的,只不过影片中没有演出来怎么渡海就是了。

所以,按照这种逻辑,影片就是5层结构了。

当然,其实还有一个很重要的问题,就是最后那个陀螺会不会倒,按照片中说法,倒了说明已经回到现实,不倒就说明还在梦中。网上看到说导演告诉记者,他也没法解释,大囧。也许导演就是设计这么一个开放式结尾让大家自己瞎琢磨,逗你玩吧。不过我们也可以按照自己的理解推断一个结果,为一个可能的续集设计故事背景,自娱自乐也无妨嘛。

比如我就这么设想,陀螺会倒下,那是否就是说这是现实了?非也,这仍然是在梦中,包括影片前半段的故事,Cobb一直是在梦中,Mal没有死,她在被Cobb植入那个limbo不是现实世界的想法后不仅仅同Cobb回到Cobb所认定的“真实世界”,还发现这个“真实世界”也不是真实的,于是跳楼自杀回到了更上一层,留下Cobb固执的认为他已经到了“真实世界”。这样其实就又变成了6层,只不过最上面一层是隐含在故事中的。

陀螺之所以会倒是因为,这个陀螺是在这一层造的,所以只能通过陀螺判断是否是在这一层之下的梦境层,而在这一层中就失效。

那为什么Mal没有再回到“真实世界”唤醒Cobb呢?我的设计是,这是Cobb的梦,由于Cobb的固执认死理,由于这种强烈的主观意识导致Mal没法再次正常进入Cobb的“真实世界”,或者进入了但Cobb看不到她。于是,续集的主题就有了,Mal的Cobb唤醒大行动,Mal由于无法进入Cobb的“真实世界”梦境,找来高手,设法跳过这一层,进入 Cobb的“真实世界”之下的第一、二、三甚至是limbo层去唤醒说服Cobb认清事实真相,这时Cobb潜意识中的Mal也再次出现,处处阻挠真正的Mal要把Cobb留在梦中,Mal的演员一人饰两角,一正一反,争夺亲夫。片名就叫《Inception之拯救老公Cobb》,哈哈哈哈,我也可以作编剧了。

By iakula

2010-09-05

Birdnest的500错误

在gae上搭了个twitter api,但是用gravity时出现了500错误,google到解决办法,打开code.py,找到第85行,改成如下形式:

1 if ua.find(jibjib) >= 0:
2 pass#socket.setdefaulttimeout(60)
3 elif ua.find(zh-CN) >= 0:
4 #raise Exception(‘unknown error’)
5 pass#socket.setdefaulttimeout(2)
6 else:
7 pass#socket.setdefaulttimeout(2)

Javascript引入外部文件的一个问题

在网页中引入外部js文件的时候必须要使用<script type=”text/javascript” src=”somewhere/jacascript.js”></script>这样的形式,而不能写成<script type=”text/javascript” src=”somewhere/jacascript.js”/>的形式,否则页面会在某些浏览器中无法正常解析,浏览器仍然会去寻找</script>,如果找不到,后面的代码无效,如果找到了页面后面其他的地方的</script>,会把这个新找到的</script>作为这个引入外部文件操作的结尾,依然会导致出错。

实验了一下,chrome5下可以写成这种形式,但ie8和ff3.5就不行

无法从SUN官网下载JDK的问题

好像好长时间了,sun官方网站上的jdk等软件无法下载,页面打得开,但就是下载不下来,在网上搜,发现很多人都碰到这个问题,找了好久终于发现有人提到是dns解析问题,把sun的下载站的ip解析到一个不正确的地址上了,解决方法是在host文件里加上63.150.131.203 cds-esd.sun.com这么一条就可以了。另外貌似用opendns或google的dns解析也可以。