Category: 未分类

  • 911 十周年

    十年前这天还在上中学,911袭击发生的时候在中国已经是晚上了,我们第二天才知道。记得大部分同学都特别兴奋,庆祝美帝国主义被打击了。没错,南斯拉夫大使馆被美国人炸了,我们上街游行,还举着“打倒美帝国主义”的标语。

    去年有机会去美国,在波士顿旁边一个叫 Newton 的小城马路旁边就看见一个911的纪念碑,类似的纪念碑在整个美国肯定有不少。后来到了纽约,世贸中心遗址还在施工,应该是在建纪念碑吧。911给我留下的印象除了狂热的兴奋之外没有更多,即使到了遗址也没有太多感觉,虽然我已意识到当初的兴奋是多么变态。

    回来之后一个晚上我又开始看911的资料,从网上找了各种视频及录音。看到大厦在烈火中倒塌,烟尘将曼哈顿覆盖,开始体会到美国人当时的痛苦。记得在网易公开课上看到 MIT 的 Introduction to Algorithms 课程第三节似乎正好是2001年9月12日,Charles Leiserson 教授在讲课之前讲了好久与恐怖袭击相关的话题,声音哽咽。大致记得他说如果你有亲人或朋友在纽约,今天不来上课可以理解。他还劝大家都去献血,有机会来美国最好的大学读书,现在是回报社会的一个机会。别说“我来读书已经交过钱了”,在这里读书的花费远超你交的学费。可惜这个视频已经被替换掉了,如果谁有原来视频的话请分享一下。

    最后推荐此文——双子塔与清真寺——美国的十年之殇,并盗用图片:

    世贸大厦纪念光柱

  • AVIS国际租车体验 – 八达岭野生动物园

    [本文有特效无法在 RSS Reader 里展示,请查看原文]

    咱不是北京人,连暂住证都没有,严格说这就叫偷渡吧?本来想混个五年,求人给个买车买房的机会,结果发现前两年的个人所得税都交到上海去了。在咱社会主义,一个好处就是除了拿工资都不用缴税,所以我这两年根本就不是北京的纳税人。扯远了,嘿嘿。4月份和老婆同时拿到驾照(没学车的同学们赶紧的,推荐东方时尚,不用费脑筋考虑贿赂教练。别的地方看似便宜,加上隐性的就贵了),一直没怎么开过车。前阵子找了个周末,事先在 AVIS 订了车,又在团购网站买了59元一张的野生动物园门票。

    从来没租过车,加上国内环境恶劣,订车之前还在网上查了好多评价,发现其它的租车公司都有人遇到一些不愉快的情况。也许是有的人真的开车不小心,故意给租车公司恶评。但是我有个朋友亲身经历,倒车不小心碰了一下,收车的人要好几百块钱(停驶费),他给了对方一点钱(当然进个人腰包了),就说没事了。有这个情况发生,肯定也就很容易出现恶意勒索的事情。了解到这些心里比较忐忑,最后选择了虽然比较小但是没有负面评价的 AVIS. 网上关于真实租车体验的文章特别少,大多都是租车公司自己的 PR,也许大家都很忙,只在有牢骚的时候才来发泄吧 🙂 我分享以下自己的经历,也算给别人点参考和帮助。

    我们在 AVIS 建外 SOHO 店(离永安里地铁近,从国贸要走一段)取车,店面很小,店员态度很好很热心。用身份证、驾照、信用卡(预授权5000,还车取消)办完手续,店员把车取来停在门口,比较细心地帮验车并在验车单子上对已有划痕作标记。租金里已经包含了基本保险,他们还提供更多保险,我买了 CDW1, CDW2, WDW,比较放心……GPS 是15一天,我自己在京东买了个带着。没开过自动档,他简单给我介绍了一下。订的是雪佛兰新乐风:

    从AVIS租到的车

    刚上路比较紧张,一出去上了建国路转三环,前面又是国贸那么复杂的路口,我差点就从掉头的道走了,晃晃悠悠拐回转弯道,晃晃悠悠上了三环主路。从三环往京藏拐,比较堵,第一次挤着并线,很费劲很紧张。出了收费站又要挤着并线,差点追尾。离开收费站就好了,过了一会就熟悉了,并线、超车都很简单。一开始我还按新手规矩,不上快车道,后来发现有的车实在太慢,就破戒了……除了自己安全行车外还得注意,乱开的车太多了。有开个破跑车乱窜的,有从应急车道超车的,最危险的是车身很长的公交车或者卡车还没超过我就开始并线。

    终于开到野生动物园,很有成就感。可是一不小心开到自驾的入口,我们原本不想开车进去,怕车被猛兽划一下子麻烦,不过既然到了门口就一狠心,花35块买了个车的票开进去了。这时发现一个很郁闷的事,油量到红线了,没经验不知道能跑多远。硬着头皮走吧,千万别没油困在猛兽区了!

    以下插入野生动物园照片:


    彷徨的白虎,凶悍的黑熊,威猛的狮子

    IMG_5582
    黑熊
    狮子
    狮子

    马来熊很可爱,嘴馋,据说会开车门,吃东西时坐地上双手捧着吃(还是有不少人投喂的)。鼠标放图片上看台词(blog新功能,如果看不见请强制刷新网页)……

    IMG_5600

    动物园里独特的创收方式。小老虎像鸡一样被关在笼子里,游人来拍照就揪一只出来,任由人摆弄姿势。小老虎明显不舒服,有时候腿着不了地乱蹬,游人全然不顾,伸手做一个“二”的手势拍照。它们一天不知道被多少只脏手摸过……起码你给人戴个一次性手套!

    被虐待的小老虎

    离开猛兽区,停车去看了猴子(好玩还得数猴子)。

    猴子


    第一次停车,走了半个广场才想起来忘记锁车了……看完猴子之后匆忙开车下山,路上的温顺动物都没仔细看,怕没油。

    出门之后直奔加油站,说加20升,他们说你凑个整钱,我就问20升大概多少钱,他们算了一下,说170吧,我就加了170. 我下车一看,7.85一升,他们数学学得真好。加好油之后上高速,去小汤山旁边的九华山庄住了一晚上。去八达岭的时候没感觉上坡,但是回来路上好像坐飞机似的,耳鸣,搞得晚饭都没食欲。第二天早上起来出发做了个错误的决定,打算去小汤山吃早餐,结果往那个方向一走就堵住了。过了好久从旁边叉路出去,绕了个大圈,跟着一个跑车在非机动车道上逆行终于逃脱,耽误了不少时间,直接就上高速往 AVIS 门店方向走了。导航让我从国贸桥右转到建国路,然后掉头。可是到了建国路,发现已经禁止掉头了,还站着警察叔叔。又绕了个大圈,最后终于到了 AVIS 门口,第一次自己开车一路无事,心里觉得这真是个奇迹,可是它就是发生了(当然有老婆在旁边做观察员)。

    这次收车的是个 MM,直接让我开到地库,简单看了一下就办了手续。取消那个5000的预授权后又做了500的预授权,作为违章押金。AVIS 的里程限制比较低,只有200公里每天,这次我们控制得真完美——199公里……出门后不久发现 iPod 耳机线丢了,回去后店员 MM 又陪我们到车里找回来。

    正好第二天拍婚纱照,回家才知道也要用车,让工作室帮忙联系的话一天350,这还不如自己租呢,于是我又打电话订了一天。第二天取车,店员(还不是前次收车的那个)还记得我还车的时候油多出来不少,因为租的是和上次同一辆车,还按第一次出车的时候油量,并且算上他们出去洗车之类的,又减掉三个。这样的态度真的让人感到非常舒服!在办手续的时候他们会提醒如果多加油是不退的,但发生了之后还是会站在客户的角度考虑问题。这次还车的时候,是把手机忘在副驾驶位了,收车的帅哥把车开到地库后发现了,回来给的我。

    几点补充……

    1. 虽然学的手动档,在市里新手上路开自动档还是挺好的,操作简单不用费心,可以有精力去观察周围的情况。并线的时候需要加速也简单。要是手动档,手忙脚乱的容易出事。即使自动档我也在动物园门口想刹车的时候踩了油门……

    2. 似乎有个租车公司(好像是1hai)不让新手买这种小额责任免除的保险。AVIS 只限制一年以下驾照的不能租比较贵的车。反正即使买了保险,大家还是要注意爱护车子,一方面是个习惯问题,另一方面如果谁都给撞,长远来看还不是得提高租金以覆盖成本吗。

    3. AVIS 网站用 Firefox 访问有小问题,chrome 问题少点,最好还是 IE。

    4. AVIS 好像在上海做得比较大,还有便宜的 Smart 可以租(体验一下奔驰,哈哈),北京我问了一下好像一共还没有上百辆车,国内覆盖的城市也相对比较少。

    AVIS 做得确实不错,毕竟是从国外直接拿来不少经验。希望国内租车市场越来越成熟吧。

  • Ubuntu Linux 鼠标插上时禁用触摸板

    在目前这个笔记本上装了 Ubuntu 11.04,多次遇见了触摸板干扰鼠标的情况。一般是鼠标按键失效,移动还可以。一开始我还以为鼠标坏了呢,心想正好有机会换个无线的,后来发现是触摸板的问题,只需按一下触摸板左键即可恢复正常。刚才又出现这种情况,让我误以为是 X 出毛病了,切换到另一个 tty,等 kill 掉 Firefox 的时候想明白了,按了一下触摸板。以前都是想禁用的时候输入命令 synclient TouchPadOff=1,不过这次是一定要彻底解决这个问题。

    这里有个脚本 mouseSwitcher.sh,内容如下:

    #!/bin/bash
    #
    # Toggle touchpad on and off
    #
    # Author: Heath Thompson
    # Email:  [email protected]
    #
    # For startup wait for desktop to load first.
    while true
    do
        if ps -A | grep gnome-panel > /dev/null; 
        then
            echo 'X loaded'
            break; 
        else
            echo 'X not loaded, waiting...'
            sleep 5
        fi
    done
    #
    # Check to see if appletouch is running
    # if lsmod | grep appletouch > /dev/null; 
    # then
    #   echo " * Appletouch enabled"; 
    # else
    #   echo " * Appletouch either not working or not installed"
    #   killall mouseSwitcher
    # fi
    
    while true
    do
        # 'xinput list' will list all input devices x detects
        # I could reference my usb mouse by ID but I'm afraid that if I plug
        # another device in before my mouse, it might not have the same ID each
        # time.  So using the device name makes it relatively fail-safe.
        if xinput list 'Microsoft Microsoft? 2.4GHz Transceiver v5.0';
        then
            # Found my usb wireless mouse
            # Disable everything on the Touchpad and turn it off
            synclient TouchpadOff=1 MaxTapTime=0 ClickFinger1=0 ClickFinger2=0 ClickFinger3=0; 
            # Ends all syndaemon capturing which may have been used to monitor the touchpad/keyboard activity
            killall syndaemon
        else
            # My usb wireless mouse isn't present we need the touchpad
            # Reenable Touchpad and configure pad-clicks
            # RTCornerButton is the Right Top Corner on the touchpad
            #   The value 3 maps it as the right click button
            # RBCornerButton is the Right Bottom Corner on the touchpad
            #   The value 2 maps it as the middle click button
            synclient TouchpadOff=0 MaxTapTime=150 ClickFinger1=1 ClickFinger2=2 ClickFinger3=3 RTCornerButton=3 RBCornerButton=2;
            # Forces break of touchpad functions while typing if the touchpad is enabled.
            # Adds a 3 second interval following keyboard use which helps to prevent the
            # mouse from jumping while typing & resting hands on restpad or the touchpad
            syndaemon -i 3 -d;
        fi
    
        # wait 2 seconds and poll the mouse state again
        sleep 2
    done
    
    sleep 15
    

    但是在我系统里,xinput list 用设备名字做参数就是找不出来。以下是 xinput list 的结果:

    ⎡ Virtual core pointer                      id=2    [master pointer  (3)]
    ⎜   ↳ Virtual core XTEST pointer                id=4    [slave  pointer  (2)]
    ⎜   ↳ PS/2 Mouse                                id=12   [slave  pointer  (2)]
    ⎜   ↳ AlpsPS/2 ALPS GlidePoint                  id=13   [slave  pointer  (2)]
    ⎜   ↳ Microsoft  Microsoft Basic Optical Mouse v2.0     id=10   [slave  pointer  (2)]
    ⎣ Virtual core keyboard                     id=3    [master keyboard (2)]
        ↳ Virtual core XTEST keyboard               id=5    [slave  keyboard (3)]
        ↳ Power Button                              id=6    [slave  keyboard (3)]
        ↳ Video Bus                                 id=7    [slave  keyboard (3)]
        ↳ Power Button                              id=8    [slave  keyboard (3)]
        ↳ Sleep Button                              id=9    [slave  keyboard (3)]
        ↳ AT Translated Set 2 keyboard              id=11   [slave  keyboard (3)]
        ↳ Ideapad extra buttons                     id=14   [slave  keyboard (3)]
    

    但是 xinput list 'Microsoft Microsoft Basic Optical Mouse v2.0' 就找不到:

    unable to find device Microsoft  Microsoft Basic Optical Mouse v2.0
    

    好像也有别人遇到这样的问题,看这个 bug. 不过换一种思路即可解决,根据我的鼠标的名字,把那一行换成这样:

      if [[ "$(xinput list | grep -F "Optical Mouse")" ]]
    

    还有个问题,如果找不到鼠标,该脚本会不停地创建 syndaemon 进程,过一会机器就几乎死掉。所以在创建之前应该先检查一下是不是已经有这个进程了:

    if [[ -z "$(pidof syndaemon)" ]]
    then
      syndaemon -i 3 -d;
    fi
    

    执行该脚本,把鼠标插拔测试以下,可以用了。于是 chmod +x mouseSwitcher.sh,然后在 System Preferences 的 Startup Applications 里添加上,以后就不用操心了。

    修改后的完整脚本如下:

    #!/bin/bash
    #
    # Toggle touchpad on and off
    #
    # Author: Heath Thompson
    # Email:  [email protected]
    #
    # For startup wait for desktop to load first.
    while true
    do
        if ps -A | grep gnome-panel > /dev/null; 
        then
            echo 'X loaded'
            break; 
        else
            echo 'X not loaded, waiting...'
            sleep 5
        fi
    done
    #
    # Check to see if appletouch is running
    # if lsmod | grep appletouch > /dev/null; 
    # then
    #   echo " * Appletouch enabled"; 
    # else
    #   echo " * Appletouch either not working or not installed"
    #   killall mouseSwitcher
    # fi
    
    while true
    do
        # 'xinput list' will list all input devices x detects
        # I could reference my usb mouse by ID but I'm afraid that if I plug
        # another device in before my mouse, it might not have the same ID each
        # time.  So using the device name makes it relatively fail-safe.
      if [[ "$(xinput list | grep -F "Optical Mouse")" ]]
        then
            # Found my usb wireless mouse
            # Disable everything on the Touchpad and turn it off
            synclient TouchpadOff=1 MaxTapTime=0 ClickFinger1=0 ClickFinger2=0 ClickFinger3=0; 
            # Ends all syndaemon capturing which may have been used to monitor the touchpad/keyboard activity
            killall syndaemon &> /dev/null
        else
            # My usb wireless mouse isn't present we need the touchpad
            # Reenable Touchpad and configure pad-clicks
            # RTCornerButton is the Right Top Corner on the touchpad
            #   The value 3 maps it as the right click button
            # RBCornerButton is the Right Bottom Corner on the touchpad
            #   The value 2 maps it as the middle click button
            synclient TouchpadOff=0 MaxTapTime=150 ClickFinger1=1 ClickFinger2=2 ClickFinger3=3 RTCornerButton=3 RBCornerButton=2;
            # Forces break of touchpad functions while typing if the touchpad is enabled.
            # Adds a 3 second interval following keyboard use which helps to prevent the
            # mouse from jumping while typing & resting hands on restpad or the touchpad
        if [[ -z "$(pidof syndaemon)" ]]
        then
          syndaemon -i 3 -d;
        fi
        fi
    
        # wait 2 seconds and poll the mouse state again
        sleep 2
    done
    
    sleep 15
    
  • D-Link DWL G122 与 Linux 驱动

    从朋友那儿借了个联想的破笔记本竟然没有无线网卡,这样的配置在这年头显得有些奇特。于是在京东买了一个友讯 (D-Link) DWL-G122 无线USB网卡,省得拉网线麻烦。

    这只有1G内存的破电脑还装着 Windows 7,慢得要死。今天装了一个 Ubuntu 11.04,装的时候想到这个 USB 无线网卡可能在 Linux 里会有安装驱动的麻烦。装完之后也没多想就开始倒腾 Linux 驱动。先把 D-Link 给的 Windows 驱动盘插上,看到里面的驱动文件名是 rtl8192su.sys

    D-Link DWL-G122

    于是在 Realtek 网站的下载页面搜索 rtl8192su,找到了 Linux 驱动的下载。看到是11年7月29日更新的,还挺勤快!不过是 for kernel 2.6.37 and earlier,可是 Ubuntu 11.04 的 kernel 恰好是2.6.38!不管了,先装上试试。解压 zip 之后,进入 driver 目录又解压 .tar.gz,make, make install, sudo depmod -a,抬头一看,咦,右上角有个无线的标志啊,真管用!点开之后选择我的路由器,输入密码,连上了!

    比较兴奋,于是想在 blog 里把过程简单记录一下。写了几个字以后感觉有点不对,好像我装好系统后 ifconfig 就看到了 wlan0… 应该是 Ubuntu 本身就已经支持这个 USB 无线网卡了,只是没有主动让我选择一个无线网络吧。我又跑到下载的驱动目录里,make uninstall,重启系统。果然,本来就可以上网的!本来此文的标题是 “D-Link DWL G122 安装 Linux 驱动”,我又舍不得丢掉已经写出来的东西,就把“安装”改成了“与” 🙂

    记得六七年前尝试使用 Linux 的时候真的很有难度,装个系统得折腾好久。装完之后还有网络、显卡等各种驱动等着折腾,当然,还有可恶的中文输入法(当时我都觉得中文输入法是阻碍 Linux 在国内发展的一个重要原因了)。现在安装 Linux 不比 Windows 复杂了,新的 kernel 对新显卡、网卡的支持又相对比较好,另外中文输入法点几下配置就可以用,实在是方便多了。可惜还是缺少一个优秀的、一致的 GUI 系统,抄 Mac 也好,抄 Windows 也好,只能得其皮毛。

    又想起前些天一帮人从京东使用 .NET 开始争论 Linux 和 Windows 各自的优势了。我觉得吧,即使 Windows 非常棒,即使微软白给 Windows 授权,也还是 Linux 成本低。了解 Windows 皮毛容易,深入掌握精髓可就难了。就像 .NET 程序员一大把一大把的,但是真正理解 .NET Framework 的牛人没几个。或许大多情况下,探索精神或是别的某种性格导致了不同的选择。

  • Python 多进程日志记录

    刚开始用 Python 做 web 开发的时候我就想一个问题,如果 Python 应用需要自己记录一些比 accesslog 更详细的日志(使用 Python 的 logging module),又有多个进程,怎么办最好呢?多个进程往同一个日志文件写入会不会出问题?

    最近有个在 Apache 里用 mod_wsgi 运行的程序,设置了4个 process. 最初没有设置日志的 rotation,看起来一切正常。有一天设定了每天 midnight rotate(换成 TimedRotatingFileHandler), 第二天就出问题了,前一天的日志完全丢失,当天日志分散在前一天和当天的两个文件里,并且两个文件都在增长。比如今天是2011-08-14,现在去观察就会发现昨天的 customlog-20110813 和今天的 customlog 两个文件都在被写入。

    看了一下 TimedRotatingFileHandler 的 doRollover 方法:

        t = self.rolloverAt - self.interval
        if self.utc:
            timeTuple = time.gmtime(t)
        else:
            timeTuple = time.localtime(t)
        dfn = self.baseFilename + "." + time.strftime(self.suffix, timeTuple)
        if os.path.exists(dfn):
            os.remove(dfn)
        os.rename(self.baseFilename, dfn)
        ...
    

    这就了然了。每个进程在过了 rotate 时间点之后写第一条日志的时候,都会执行这个 doRollover,先看有没有 customlog-20110813 存在,存在的话删掉,把 customlog 改名为 customlog-20110813(注意这一步,文件名只是文件的一个属性,如果有进程已经打开该文件正在写入,并不会受影响,除非文件被删除),然后往新的 customlog 里写入。等四个进程都执行完这个方法的时候,就是一团糟了,不仅昨天的日志完全被删除,今天的日志也会有一小部分被删掉了。

    多进程 (Multiprocessing) vs. 多线程 (multithreading)

    假如只有一个进程,就不用操心这些了。我这个 qingbo.net 一直都是设置1个进程。于是我在 Stack Overflow 上问,如果 multithreading 就够了,为啥还要 multiprocessing 呢?回答者 Graham 好像是 mod_wsgi 的作者,从他的回答看,对于 Python 来说,由于 GIL 的限制,多进程才能利用多 CPU 或 core 的架构。

    好吧,想想有什么办法可以解决这个问题。

    Write separate files

    取得当前进程 ID (os.getpid()),然后把这个 pid 作为日志文件名的一部分,这样各个进程的日志操作就完全不会互相影响了。

    缺点是当服务器重启的时候,新的进程会有新的 pid,于是旧的文件不会被改成带有日期标识的名字,看起来不是那么干净。不过这个问题通过写一个简单的 shell 脚本即可修复,可以放在 crontab 里每天凌晨检查前一天的日志。

    结合 WatchedFileHandler 与 TimedRotatingFileHandler?

    以下是 WatchedFileHandler 的 emit 方法:

    def emit(self, record):
        """
        Emit a record.
    
        First check if the underlying file has changed, and if it
        has, close the old stream and reopen the file to get the
        current stream.
        """
        if not os.path.exists(self.baseFilename):
            stat = None
            changed = 1
        else:
            stat = os.stat(self.baseFilename)
            changed = (stat[ST_DEV] != self.dev) or (stat[ST_INO] != self.ino)
        if changed and self.stream is not None:
            self.stream.flush()
            self.stream.close()
            self.stream = self._open()
            if stat is None:
                stat = os.stat(self.baseFilename)
            self.dev, self.ino = stat[ST_DEV], stat[ST_INO]
        logging.FileHandler.emit(self, record)
    

    每写一条日志之前它先判断日志文件是不是被改名了,如果是的话就重新创建一个。如果 TimedRotatingFileHandler 在 doRollover 之前做这么一个检查,不就解决问题了吗?不过实际上这又牵扯到进程间同步的问题,还挺复杂的。

    WatchedFileHandler 结合 cron job

    一个取巧的办法是使用 WatchedFileHandler,并在 crontab 里每天零点添加一个任务,把日志文件改名。于是所有的进程在之后写第一条日志的时候会发现这一点,开始往新的文件里写入。

    我没有尝试这个办法所以不知到是不是有问题。但是它的前提是没一个写入操作是一个 atom operation,否则两条日志有可能混杂在一起,就坏了。我不太了解,不过这里有个讨论,看起来每条日志不超过 4K 的话是安全的。该文还展示了另一种办法——

    将日志发给一个独立的进程

    Python 的 logging module 还提供了一个 SocketHandler,用来把日志通过网络发送出去。文档里有一个非常基本的服务器端的实现。有人还基于 Twisted 写了一个更好的 logging server.

    但是这样我们除了需要维护 web server,又要维护 logging server. 万一 logging server 挂了怎么办?我有点担心。

    把日志发给 web server

    其实也是发给一个独立进程了。mod_wsgi 提供了一种途径,可以把 debug 信息写到 Apache 的 errorlog 里。不过把应用日志跟服务器 errorlog 混在一起,这显然不是什么好办法。

    比起多进程的解决方案,我更倾向于使用单线程省去这些烦恼。一方面是我的服务器比较弱,只有两个 core 🙂 正如 Graham 所说,除了 python code,还有许多处理比如接受、分发请求,发送响应回客户端,处理静态文件请求等都是在 Apache 的 C code里执行的,所以不会让一个 core 忙不过来另一个却闲着。我倒没有实际经验看看单个 Python 应用进程在4核或8核机器上面对大并发量的情况。

    单进程的另一个好处,可以不用 memcached 之类的东西,自己直接在进程的内存里方便地作缓存。如果有多个进程这么搞,那就太浪费内存了。

  • 使用第三方网站作为用户认证系统

    很早的时候就有人发明了 OpenID,希望解决用户在每一个网站都重复注册流程的问题,但是由于种种原因,这个事情好久没什么起色。我曾经也弄过 OpenID,后来发现由于不怎么常用,我连 URL、密码之类的都记不住,还不如在每个网站上都注册一下。从网站的角度看,他们也不愿意把网站的入口交给第三方——这个 OpenID provider 的网站挂了怎么办?直到出现 Google/Yahoo! 这样级别的大佬作为 OpenID provider 还比较靠谱。第一,它们足够稳定;第二,它们本身就是许多广为使用的服务的提供商,这个也让人可以信赖第一点是成立的,它们从自身利益出发就有足够的理由去保障。到了这个阶段,就不是 OpenID 的成功了,而是 Google/Yahoo! 的成功,或者说第三方登录的成功,选择什么协议已经不重要了。

    现在国外许多网站都使用 Facebook Connect,大部分都是为了让自己的用户把 Facebook 账户关联起来,好从 Facebook 拿到用户的数据,或者让用户把本站的信息向 Facebook 发送,利用 Facebook 的巨大社交网络作传播。所以大部分的 Facebook Graph 教程,都在讲怎么往自己网站加个 Facebook Connect 的按钮,如何重定向,如何拿到用户的隐私数据等等。

    不过我最近在想做一个新的网站的时候,就想直接依赖第三方的用户认证系统,而不自己实现了。向第三方网站 pull/push 数据只是一个附带产物。以下只是我很粗浅的想法,还没有实现,等将来有了实际经验的话也许再回来更新一下这篇文章吧。

    基于 cookie 的用户名密码认证

    每个做过网站的都记得一般是怎么用用户名密码做用户登录的。数据库里有一个表来记录用户名和 hash 过的密码,登录时,用同样的算法 hash 提交过来的密码,看跟数据库里的记录是否一致。如果登录成功,创建一个或几个 cookie,里面记录该用户已经登录成功,并且有算法保证这个 cookie 的信息(用户名、过期时间)不是伪造的(许多使用 HMAC 算法)。这样用户下次请求的时候,就不再需要重新提交用户名密码了,直接检查请求中附带的 cookie 即可知道他是谁。

    一直遵从这么一个思路,我都快忘了它是干嘛的了。如果一个网站有 UGC,那么很多情况下我们都需要知道访问者的身份。我们所需要的就是身份认证,让用户可以安全地保存他的内容或秘密。

    Facebook Connect 作为身份认证服务

    使用第三方用户认证的概念就是,以前我们直接问用户要口令,来确定来者是谁,现在转而去一个可信的人(Facebook/Google)那儿去查。一旦查清楚身份,我们就给用户一个临时的通行证(cookie),在一段时间内就不用再要密码或者再去第三方哪里查了。看起来很显然,不过我花了点时间才想清楚这一点。所以现在我们最需要的就是从 Facebook/Google 那儿要一个 ID,就好像一个人的名字、身份证号码一样。有一些选择:

    • Facebook ID 是一个号码,在 Facebook 的用户系统中是绝对不会重复的
    • 申请授权的时候,同时申请得到用户 email 的权限。email 也是一个非常合适的 ID,甚至比 Facebook ID 还好,因为它在 Facebook 之外也是唯一的。

    Stack Overflow 的身份认证

    上面也提到了,一旦确认身份之后,还是用 cookie 作为临时通行证。我们可以把 Facebook ID 或者 email 放在 cookie 里,同时放一个 hash 来保证它的真实性。Stack Overflow 就是一个很好的例子,我觉得它是使用 email 作为 ID 的,可以这么来验证这一点:

    1. 确保你的 Google/Facebook 账户使用的 email 地址是一样的
    2. 通过 Google 登录 Stack Overflow,创建账户
    3. 退出登录,然后用 Facebook 重新登录
    4. 这时候你就看到,进入了刚才通过 Google 登录创建的账户了!

    Stack Overflow 在一个名为 “usr” 的 cookie 里保存认证信息,并且 cookie 的有效期是6个月(也许应该加个 “remember me” 的选项吧?)。所以即使退出 Google/Facebook,也不会影响在 Stack Overflow 的登录状态。把这个 cookie 复制到另一台计算机上,然后访问 Stack Overflow,你会发现你已经登录了。

    这样做会威胁到 Facebook 账户的安全吗?不会。Facebook 使用的是 OAuth 2.0,authorization code 和 acess code 很快就过期,即使有个人偷到了 cookie,闯入别人的 Stack Overflow 账户,他如果想通过 SO 在 Facebook 做点坏事的话,SO 也需要重新走个流程,获取 authorization code 及 access code。

    Quora的身份认证

    依赖第三方的登录看起来有点危险,即使是 Facebook/Google,它们也难免有 downtme,这时候用户就无法被认证了。但是如果你得到了用户的 email,将来有一天就可以摆脱 Facebook/Google,让用户在本站建立密码。

    Quora 也是个很有意思的例子。比如最初的时候我记得 Quora 是只接受 email 注册的,我就用 gmail 邮箱注册了一个账户。过了很久以后再去登录,发现有了 Connect to Facebook 这么个按钮,就点了。然后发现我进入了原来用 email 注册的那个账户,看起来跟 Stack Overflow 是一样的道理。

    不过不一样的地方是,不管你怎么登录,Quora 都要求你在它网站上设立一个密码。Twitter 的 OAuth 不提供 Email 信息,所以如果用 twitter 登录的话,还要你输入 email. 因此 Quora 并不依赖第三方认证,它只是让用户把账户跟 Facebok/Twitter 连接起来,好利用它们扩大影响。即使 twitter 几年以后不存在了,也不影响用户在 Quora 登录。

    如果我的网站要依赖第三方的用户认证,也应适当地要一些多的权限,好利用这些 SNS 的力量。归根结底,它们的 API 就是为了这个设计的,并不只是用户认证。

  • OpenVPN 客户端在 Windows 里的配置

    自己搭了一个 OpenVPN Server,以前一直是在 Mac 里使用客户端,加上 chnroutes,用得很舒服。最近想在 Windows 里用一下,结果显示能连上,但是流量就是不从 VPN 走,很郁闷。

    试了一下,连服务器 IP 10.0.0.1 都 ping 不通。运行 ipconfig 显示 OpenVPN 创建的 interface 信息为:

        Connection-specific DNS Suffix  . :
        IP Address. . . . . . . . . . . . : 10.0.0.6
        Subnet Mask . . . . . . . . . . . : 10.0.0.5
        Default Gateway . . . . . . . . . :
    

    显然错得很厉害。看客户端日志,看服务器日志,都没找出什么原因来。最终发现是客户端配置文件里有个:

    dev tap
    

    好像网上很多教程都说 Windows 平台就用 tap,其它平台用 tun (或者是我的误解吧)。看 openvpn 官方的 manual:

    TUN/TAP virtual network device ( X can be omitted for a dynamic device.)

    See examples section below for an example on setting up a TUN device.

    You must use either tun devices on both ends of the connection or tap devices on both ends. You cannot mix them, as they represent different underlying protocols.

    tun devices encapsulate IPv4 while tap devices encapsulate ethernet 802.3.

    所以这个配置必须与 Server 的配置保持一致。果然,改成 tun 以后一切正常了。


    关于 chnroutes, 以前总是打开 Usage 就直接往下找 Windows 那块,刚发现这个已经是过时的方法了。这个老办法是先找到默认网关,然后把所有中国网段的流量都从这个默认网关走,所以应该在 OpenVPN 修改默认网关(如果服务器端设置了 push “redirect-gateway” 的话客户端会自动把默认网关改成 OpenVPN 的虚拟网卡)之前执行,否则就成了所有中国网段的流量都从 OpenVPN 走(可以用 route print * 验证一下)——没有意义。并且在我的机器上不知道为什么执行那个vbs脚本没有任何效果。

    Usage 的开头就说,OpenVPN 版本在 2.1 以上的话,直接在配置文件里写上路由规则即可,用 net_gateway 表示原来的默认网关。我试了一下,路由完全正确地添加了。此法非常简单易用,看来以后看文档还是要仔细啊。

  • 欢乐谷玩了一圈

    在北京去了几次北京游乐园,却没有去过欢乐谷。趁现在没有工作,今天去玩了一圈。早就知道暑假已经来了,估计即使是工作日小孩们也会很多,有点心理准备,于是找了一个预报有雷阵雨的天去。谁知道是大家现在都不信天气预报了,还是百折不挠长了这么大连雷劈都不怕了,去到欢乐谷门口的时候还没开门就发现长队已经排起来了。

    事先查了一下,攻略说一进去就赶紧到奥德塞之旅那儿,我就按攻略去了。八点半开门,但是大部分项目九点才开始运行,所以等了二十多分钟,坐上了第一条船。视频:

    下了船全湿了,不过是夏天,后来一个小时就干了。丛林飞车是个很初级的过山车,保护措施都不需要太完善,坐我旁边的是一个老大爷,一直张着双臂很惬意的样子。天地双雄是两个柱子,一个急升,一个急降。我去的时候只开了急降的,排队大概20分钟,急降的那一瞬间感觉挺恐怖的,不光是下面没有了支撑(过山车或者奥德塞之类的),它还给你一个向下的力。还好只是很短的时间。

    玩水晶神翼的时候排了半小时,保护装置就位之后还没出发就成了贴地飞行的姿势,完全是让人体验做鸟人的感觉,第一次玩还是很恐怖的,很刺激。下午又去玩了一次基本就不用排队了,放松心情,充分信任设备的安全性,就可以享受飞翔的感觉了。雪域金翅中午12点才开始运行,排队20分钟左右,这个悬挂式过山车速度比较快,有好多翻转的地方,身体经常感受到很大的力量,这一点比水晶神翼厉害,就是第二次再去坐我觉得也不会觉得轻松。

    后来看到了极速飞车,很酷的弹射式过山车,但是人山人海,插队的,吵架的,简直就是传说中的人道主义灾难。感叹祖国大地真是狼多肉少,当初在 LA 去 Universal Studios, 还是周末,几乎所有的项目都不需要排队,最多也就几分钟,喜欢的项目可以有机会反复玩。过山车在一段直道突然加速,然后冲上最高点,我觉得这一段如果设计在一个黑暗的洞里或者水下会更刺激(像 Universal Studios 里的 Mummies 一样)。好玩是好玩,可我不想在那儿挤3个小时。问了一下工作人员,据说常年这么多人,这可如何是好……拍了一段别人玩的视频:

    太阳神车主要是感受在谷底时的速度和嗖嗖的风,我有点理解在电影 The World’s Fastest Indian 里小孩问老头速度到了 200 MPH 的时候张开嘴是什么感觉,老头好像是说张开嘴就把你吹爆了……当然太阳神车远远没有达到这个速度。站在旁边看有时候也会遇见趣事。有个女生,设备一启动就开始不由自主尖叫,两脚紧紧缠在一起,把下面的人笑弯腰了都。

    最后做了个非常错误的决定,坐了一个欢乐风火轮,转得我开始恶心,于是回家来了。工作人员一般会在介绍的时候说如果遇到身体不适可以招手,游乐园里这种项目千万不能玩。还有一个特征是在旁边看着摇啊摇,玩的人里面却没有尖叫声。这种转啊转却不刺激的项目,容易让人头晕恶心。

    还发现欢乐谷里的所谓“高速摄影”,有很多是人肉拿着单反加长焦镜头在拍呢,祖国真是劳动力物美价廉。不过我猜他们用的是 Eye-Fi 卡吧,也算高科技了(也许是人肉速递的)。

    得到的经验就是,暑假工作日去的话,就奥德赛(我后来再去的时候都找不到排到哪儿了)和极速飞车很难排上,其它的到了下午最多排个十几二十分钟,当然也许跟天气预报有点关系。

  • 瑕疵的粉饰

    最近在看林达关于美国法治与民主进程、历史的一系列书,里面讲述的许多历史事件让人收获颇丰。但是让我经常感到不舒服的是,在描写许多事件或者人物遇到一些瑕疵的时候(但是作者可能认为是完美的),总是尝试从一个顺手的角度,比如法律或者人性,去为他们的不完美做一个合理的解释,以显示他们仍旧是最好的。

    完全没有必要,读者自有能力去分辨是非,有些东西就是错的,不需要你证明这个错误是正确的合理的。这种行为让我反感的原因可能是我已经受够了从小到大被灌输的一些完美的概念。

    今天读到最后一本,第一篇里就又遇到了这种“老套的情节”。南北战争,联邦政府做手脚“合法”地将属于南军首领的阿灵顿收入囊中。但是此处林达加了一段——

    尽管在这个过程中,我们还是看到美国的法律文化在起着一定作用,换个地方,只需一纸通令,作敌产没收即可,哪里还需要费这些周折。

    我实在有点恶心,忍不住中断阅读来 blog 里记录一下。这无疑是一件肮脏的交易,是耻辱,有些人就是能从屎里发现金子。如同现今国内政府部门招标,自己注册一个公司去中标,同时还要请一个“第三方”公司代理招标以起到“监督”作用,你批判之余,难道还要赞美一下这符合流程吗?

    是不是我思维能力太差,看到很多历史的时候,我只能惊叹,却没有足够的脑力去想明白它的对错。

  • 找一份网站开发工作

    我最近要换工作,也在 blog 里发一篇,希望能增加遇见合适互联网公司的几率。简历在这里,也可下载 pdf 版本(中文 | 英文)。

    我喜欢技术,不糟蹋技术。喜欢读英文书,包括技术、非技术。最近在读 The Pragmatic Programmer,深深认同其中的观点。喜欢通过实践学习技术,比如2年前开始对 Python 感兴趣,于是基于 web.py,把这个个人网站(原来是 WordPress)用 Python 改写。

    对新公司的期望,除了经济上的回报,还希望能进入一个喜欢、尊重技术的团队,让我可以学到更多东西,同时把能力发挥到极致。我有一定经验但不是为了卖经验的,希望双方能达到共赢。

    憋不出字来了,就这样吧。朋友们看到合适的机会帮我引荐一下,谢谢!我现在住北京,但如果其它城市有合适的机会,我很乐意离开这儿。