Freedom

淡泊以明志,宁静以致远。

理解 tornado.gen

Tornado 通过 @asynchronous decorator 来实现异步请求,但使用的时候必须将 request handler 和 callback 分离开,tornado.gen 模块可以帮助我们在一个函数里完成这两个工作。下面是官方的一个例子:

1
2
3
4
5
6
7
8
class GenAsyncHandler(RequestHandler):
    @asynchronous
    @gen.engine
    def get(self):
        http_client = AsyncHTTPClient()
        response = yield gen.Task(http_client.fetch, "http://example.com")
        do_something_with_response(response)
        self.render("template.html")

这里用到了两个 decorator 稍显复杂,第一个 @asynchronous 会首先被执行,它的主要工作就是将 RequestHandler_auto_finish 属性置为 false,如下:

web.pydownload
1
2
3
4
5
6
7
8
9
10
def asynchronous(method):
    @functools.wraps(method)
    def wrapper(self, *args, **kwargs):
        if self.application._wsgi:
            raise Exception("@asynchronous is not supported for WSGI apps")
        self._auto_finish = False
        with stack_context.ExceptionStackContext(
            self._stack_context_handle_exception):
            return method(self, *args, **kwargs)
    return wrapper

接着就是最重要的 @gen.engine,这里充分利用了 generator 的各种特性,首先来看 @gen.engine 的实现(我删减了部分代码以简化理解):

gen.pydownload
1
2
3
4
5
6
7
8
9
def engine(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        gen = func(*args, **kwargs)
        if isinstance(gen, types.GeneratorType):
            runner = Runner(gen)
            runner.run()
            return
    return wrapper

局部变量 gen 代表第一段代码里的 get 函数,因为 get 包含了 yield 语句,因此成为了一个 generator。注意这里 get 并没有被执行,只是赋给了 gen。接下来是运行 Runner 对象的 run 函数。在理解 run 之前需要知道 generator 是通过调用 next() 或者 send() 来启动,启动之后会在遇到 yield 的地方 hold 住,然后将 yield 后面的语句的返回值返回给调用者,generator 此时即处于暂停运行状态,所有上下文都会保存。再次调用 next()send() 便会恢复 generator 的运行,如果不再遇到 yield 语句就会抛出 StopIteration 异常。在恢复运行的同时 yield 语句本身会有返回值,如果是通过调用 next() 来恢复的,那么返回值永远是 None,而如果是通过 send() 则返回值取决于传给 send() 的参数。更多关于 generator 的说明请参考官方文档

结合第一段的示例代码,可以想到 run 干的工作可能就是启动 generator,然后获得 gen.Task 对象并调用 http_client.fetch 函数,等回调回来之后恢复 generator 的运行,最后将回调的返回值通过 send() 赋给 response。下面是我简化后的代码。

gen.pydownload
1
2
3
4
5
6
7
8
9
10
11
12
def run(self):
    while True:
        if not self.yield_point.is_ready():
            return
        next = self.yield_point.get_result()
        try:
            yielded = self.gen.send(next)
        except StopIteration:
            return
        if isinstance(yielded, YieldPoint):
            self.yield_point = yielded
            self.yield_point.start(self)

第 3 行检查回调是否完成,第一次运行 run 总是会返回 True。第 5 行获取回调的返回值,同样的第一次运行返回的是 None。将 None 传给 send() 启动 generator,yielded 即是 gen.Task 对象,第 12 行调用 start 开始运行我们真正需要运行的函数,对应到示例代码就是 http_client.fetch 函数,同时将 Runnerresult_callback 作为回调函数。如下:

gen.pydownload
1
2
3
4
5
6
7
8
9
10
11
def result_callback(self, key):
    def inner(*args, **kwargs):
        if kwargs or len(args) > 1:
            result = Arguments(args, kwargs)
        elif args:
            result = args[0]
        else:
            result = None
        self.results[key] = result
        self.run()
    return inner

在得到回调返回值之后再次调用 run,通过 get_result 获取返回值,最后将返回值返回赋给 response,继续 request handler 的代码流程。

幸福

三月的深圳已经渐渐有了夏天的感觉,选择这个时候出游其实是为了给自己,还有兰姐一个清明小长假前的悠闲假期。曾经的惨痛经历告诉我,如果在公共假期旅行,即使再美丽的地方也会被摩肩接踵的人群弄得失去了本来的韵味。这不是一篇攻略,虽然我会穿插着讲述一些在旅行途中的境遇。我用我的双眼,Twitter,Instagram 和 foursqure 记录下了这短暂的三天旅程。

Shall We Play a Game?

WarGames」是一部 1983 年上映的科幻电影,作为投资仅 1200 万美元的小制作,在当年赢得了近 8000 万的票房。故事发生在美苏冷战时期,那个年代的电影,多多少少都会跟核威慑有关。这两个国家不管谁先发射导弹,那第三次世界大战就会爆发。电影中 NORAD(北美防空司令部)使用了一台叫做 WOPR 的超级计算机进行战事控制,这台计算机特别的地方在于它能自己模拟战争,模拟的过程就像在玩一个游戏(game)。某一天,我们的男主角天才高中生无意中侵入了这台电脑,出于好奇和好玩,启动了核战争游戏。不料这场模拟战争误使 NORAD 以为苏联发动了袭击,一度差点引爆真正的核对抗。最后在男主角和 WOPR 创造者的共同努力下及时终止了这场「战争」。

电影中男主角的人物原型来自一位叫做 David Scott Lewis 的黑客,这位老兄之后一直在搞机器人,后来按照他自己的话来说是「sold my soul to the bigger corporations」,分别在三星、Microsoft 和 Oracle 工作一段时间之后,他来到了中国,跟清华大学合作。现在貌似又搞太阳能去了。WOPR 的创造者 Dr. Stephen Falken 的原型来自著名的物理学家 Steven Hawking(是不是名字也很像?),WarGames 最初的剧本就是根据 Hawking 来写的,曾经还打算塑造一个坐在轮椅上的天体物理学家,但因为太容易让人联想到另一部讲述冷战的电影「Dr. Strangelove」而作罢。其实 Dr. Stephen Falken 对于人工智能的深入研究,倒让我觉得更像是 Alan Turing。

WarGames 对于此后的黑客文化产生了深远的影响。它创造了「firewall」这个词汇(方校长表示感谢)。电影中男主角入侵 NORAD 时使用的技术衍生出了「wardialing」术语。wardialing 是指通过程序不断扫描电话号码来发现计算机 modem,早期的电话黑客即是使用的这种技术。著名的电话黑客(phone phreak)John Draper 因为通过 wardialing 免费打电话而闻名,后来他将这项技术教给了 Steve Jobs 和 Steve Wozniak(是的,没错,就是乔帮主),帮主他们还因此赚了不少钱(帮主果然是个好商人,从小就懂得怎么把技术转为商业利益)。John Draper 后来受雇于 Apple,但这是后话了。wardialing 之后又衍生出了 wardriving,通过扫描和收集 Wi-Fi 热点来进行攻击,之所以叫这个名字,是因为通常是在汽车里一边行驶,一边收集(这样说来 Google 的街景小车也算是 wardriving 了一把)。现在你可以在你的 iPhone 或者 Android 手机里装上一款 wardriving 软件试试看,我曾经试过,但是貌似效果不是很好。

还有一个人「深受」这部电影的影响,Kevin Mitnick,这个计算机安全界传说级的人物。当然不是说 Kevin 同学是因为这部电影走上的不归路,Kevin 同学搞入侵那会儿 WarGames 还没上映呢。Kevin 同学后来入狱时被拒绝接触任何电子设备,包括电话,是因为控诉律师相信他可以通过电话连接到 NORAD。据 Kevin 同学分析 WarGames 这部电影很大程度上使得公众相信这件事情是可以很容易办到的,其实他根本就没有入侵过 NORAD,通过电话来入侵也过于夸张。尽管如此,他还是被判单独监禁,从此传为计算机安全界的一段「佳话」⋯⋯

游戏界也从 WarGames 获益不少,1984 年同名游戏发布。2006 年一款叫做 DEFCON 的即时战略游戏发布,游戏画面与电影中 NORAD 指挥中心的大屏幕极为相似。DEFCON(defense readiness condition)是美国军方采用的警报等级,在 WarGames 电影中 NORAD 曾因模拟的苏联进攻一度将 DEFCON 等级提升到最高等级 1。著名的黑客大会 DEF CON 的名字也是来源于此。

或许 WarGames 对于电影史并没有太大的贡献,但却深深影响着那一代的 Geek 们。谨以此文献给那些逝去的先驱,RIP

GYP 简介

说起项目构建工具,Linux 用户最熟悉的恐怕就是 Autotools,它将编译安装这个步骤大大简化。但对于项目作者来说,想要使用 Autotools 生成有效的配置文件着实需要下一番功夫,用现在流行的话来说就是用户体验不够友好。对 Unix shell 的依赖,也使得 Autotools 天生对于跨平台支持不佳。

后来我从大猫同学那里听说了 CMake,CMake 使用 C++ 编写,原生支持跨平台,不需要像 Autotools 那样写一堆的配置文件,只需一个 CMakeLists.txt 文件即可。简洁的使用方式,强大的功能使得我立马对 CMake 情有独钟。在后来的使用过程中,虽然会遇到一些因为使用习惯带来的小困扰,但我对于 CMake 还是基本满意的。直到我发现了 GYP。

GYP(Generate Your Projects)是由 Chromium 团队开发的跨平台自动化项目构建工具,Chromium 便是通过 GYP 进行项目构建管理。为什么我要选择 GYP,而放弃 CMake 呢?功能上 GYP 和 CMake 很是相似,在我看来,它们的最大区别在于配置文件的编写方式和其中蕴含的思想。

编写 CMake 配置文件相比 Autotools 来说已经简化很多,一个最简单的配置文件只需要写上源文件及生成类型(可执行文件、静态库、动态库等)即可。对分支语句和循环语句的支持也使得 CMake 更加灵活。但是,CMake 最大的问题也是在这个配置文件,请看下面这个示例文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
cmake_minimum_required(VERSION 2.8)
project(VP8 CXX)

add_definitions(-Wall)
cmake_policy(SET CMP0015 NEW)
include_directories("include")
link_directories("lib")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "../lib")
set(VP8SRC VP8Encoder.cpp VP8Decoder.cpp)

if(X86)
    set(CMAKE_SYSTEM_NAME Darwin)
    set(CMAKE_SYSTEM_PROCESSOR i386)
    set(CMAKE_OSX_ARCHITECTURES "i386")

    add_library(vp8 STATIC ${VP8SRC})
elseif(IPHONE)
    if(SIMULATOR)
        set(PLATFORM "iPhoneSimulator")
        set(PROCESSOR i386)
        set(ARCH "i386")
    else()
        set(PLATFORM "iPhoneOS")
        set(PROCESSOR arm)
        set(ARCH "armv7")
    endif()

    set(SDKVER "4.0")
    set(DEVROOT "/Developer/Platforms/${PLATFORM}.platform/Developer")
    set(SDKROOT "${DEVROOT}/SDKs/${PLATFORM}${SDKVER}.sdk")
    set(CMAKE_OSX_SYSROOT "${SDKROOT}")
    set(CMAKE_SYSTEM_NAME Generic)
    set(CMAKE_SYSTEM_PROCESSOR ${PROCESSOR})
    set(CMAKE_CXX_COMPILER "${DEVROOT}/usr/bin/g++")
    set(CMAKE_OSX_ARCHITECTURES ${ARCH})

    include_directories(SYSTEM "${SDKROOT}/usr/include")
    link_directories(SYSTEM "${SDKROOT}/usr/lib")

    add_definitions(-D_PHONE)
    add_library(vp8-armv7-darwin STATIC ${VP8SRC})
endif()

你能一眼看出这个配置文件干了什么吗?其实这个配置文件想要产生的目标(target)只有一个,就是通过 ${VP8SRC} 编译生成的静态库,但因为加上了条件判断,及各种平台相关配置,使得这个配置文件看起来很是复杂。在我看来,编写 CMake 配置文件是一种线性思维,对于同一个目标的配置可能会零散分布在各个地方。而 GYP 则相当不同,GYP 的配置文件更多地强调模块化、结构化。看看下面这个示例文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
{
  'targets': [
    {
      'target_name': 'foo',
      'type': '<(library)',
      'dependencies': [
        'bar',
      ],
      'defines': [
        'DEFINE_FOO',
        'DEFINE_A_VALUE=value',
      ],
      'include_dirs': [
        '..',
      ],
      'sources': [
        'file1.cc',
        'file2.cc',
      ],
      'conditions': [
        ['OS=="linux"', {
          'defines': [
            'LINUX_DEFINE',
          ],
          'include_dirs': [
            'include/linux',
          ],
        }],
        ['OS=="win"', {
          'defines': [
            'WINDOWS_SPECIFIC_DEFINE',
          ],
        }, { # OS != "win",
          'defines': [
            'NON_WINDOWS_DEFINE',
          ],
        }]
      ],
    }
  ],
}

我们可以立马看出上面这个配置文件的输出目标只有一个,也就是 foo,它是一个库文件(至于是静态的还是动态的这需要在生成项目时指定),它依赖的目标、宏定义、包含的头文件路径、源文件是什么,以及根据不同平台设定的不同配置等。这种定义配置文件的方式相比 CMake 来说,让我觉得更加舒服,也更加清晰,特别是当一个输出目标的配置越来越多时,使用 CMake 来管理可能会愈加混乱。

配置文件的编写方式是我区分 GYP 和 CMake 之间最大的不同点,当然 GYP 也有一些小细节值得注意,比如支持跨平台项目工程文件输出,Windows 平台默认是 Visual Studio,Linux 平台默认是 Makefile,Mac 平台默认是 Xcode,这个功能 CMake 也同样支持,只是缺少了 Xcode。Chromium 团队成员也撰文详细比较了 GYP 和 CMake 之间的优缺点,在开发 GYP 之前,他们也曾试图转到 SCons(这个我没用过,有经验的同学可以比较一下),但是失败了,于是 GYP 就诞生了。

当然 GYP 也不是没有缺点,相反,我觉得它的「缺点」一大堆:

  • 文档不够完整,项目不够正式,某些地方还保留着 Chromium 的影子,看起来像是还没有完全独立出来。
  • 大量的括号嵌套,很容易让人看晕,有过 Lisp 使用经验的同学可以对号入座。对于有括号恐惧症,或者不使用现代编辑器的同学基本可以绕行。
  • 为了支持跨平台,有时不得不加入某些特定平台的配置信息,比如只适用于 Visual Studio 的 RuntimeLibrary 配置,这不利于跨平台配置文件的编写,也无形中增加了编写复杂度。
  • 不支持 make clean,唯一的方法就是将输出目录整个删除或者手动删除其中的某些文件。

如果你已经打算尝试 GYP,那一定记得在生成项目工程文件时加上 --depth 参数,譬如:

$ gyp --depth=. foo.gyp

这也是一个从 Chromium 项目遗留下来的历史问题。

也许你根本用不上跨平台特性,但是 GYP 依然值得尝试。我编写了一份 GYP 配置文件的模板,有兴趣的同学可以参考。GYP 和 CMake 分别代表了两种迥异的「风格」,至于孰优孰劣,还得仁者见仁,智者见智。

Octopress Rocks!

今天开始尝试 Octopress,之前也有耳闻,当时立马被它的 Geek 气息吸引,今天详细了解,Octopress 真不愧为「A blogging framework for hackers」。我用了多年的 Blogger 博客也终于寿终正寝,如果你是一个非 hacker 博客作者,Blogger 绝对值得推荐(满足 GFW 三定律)。

Another WordPress?

Yes。Octopress 具备一个博客应当具备的所有功能,文章、评论、页面、分享、RSS、搜索、Archives 等等。

No。正如 Octopress 网站介绍所说:A blogging framework for hackers,重点就在最后那个 hackers。没有了 WordPress 的后台界面,写博客需要的工具仅仅是 Ruby、Git、Markdown 和你喜爱的编辑器。如果你是一个 hacker,那你对这些工具不会陌生,相比 WordPress 蹩脚的后台页面,Octopress 提供的写作方式会让你非常喜爱。

写作

博客最重要的功能就是写作,写作就像程序员编写代码,如果不能提供舒服的方式,那简直是一种自虐。事实已经证明 HTML 不是一种好的写作方式,因此 WordPress 这类博客提供了所见即所得编辑器,但对于喜欢精确掌控的 hacker 来说这还不够,于是类似于 Markdown 这样的标记语言逐渐在圈内盛行。这类标记语言最大的好处就是让作者不用关心文章的样式,而专注于文章的内容。这很重要,一篇文章的精髓在于文字,如果过多地被样式困扰,精力便会分散,也必然不会思考出更好的文字。类似的比较还有 Word 和 LaTeX,当然这种观点也是仁者见仁,智者见智。

Octopress 原生为我们提供了 Markdown 支持,需要写一篇新博客了?打开你喜欢的编辑器,使用 Markdown 语法开始书写即可。WordPress?虽然也可以添加 Markdown 支持,但不免显得蹩脚。

语法高亮

这个功能对程序员来说尤为重要,但至今没有博客提供原生支持,这也是最大的遗憾。Octopress 彻底颠覆了这种局面,语法高亮变得如此顺其自然。

rename.pydownload
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Batch rename utility. <https://github.com/xiaogaozi/princess-alist>
# Copyright (C) <2011>  xiaogaozi <[email protected]>
# 
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

import os
import re
import sys

def usage():
    """Usage"""
    print "Usage: rename.py expr files"

def main():
    """Main progress."""
    if len(sys.argv) < 3:
        usage()
        sys.exit(1)

    expr = sys.argv[1]
    m = re.search(r'^[sy]/([^/]*)/([^/]*)/$', expr)
    if m is None:
        sys.stderr.write("expression incorrect\n")
        sys.exit(1)
    re1 = m.group(1)
    re2 = m.group(2)  # actually in substitute mode this portion is not regular expression

    for oldfile in sys.argv[2:]:
        d = os.path.dirname(oldfile)
        oldname = os.path.basename(oldfile)
        newname = ''
        newfile = ''
        if expr[0] == 's':  # substitute
            newname = re.sub(re1, re2, oldname)
            newfile = os.path.join(d, newname)
            os.rename(oldfile, newfile)
        elif expr[0] == 'y':  # transliterate
            pass
        print oldfile, '->', newfile

if __name__ == "__main__":
    main()

版本控制

程序员已经被版本控制惯坏了,只要能纳入版本库,就统统放进去。WordPress 拥有同样蹩脚的版本控制,显然不足以满足 hacker 的需求,Octopress 为我们提供了 Git 原生支持,一切一切都为你所控,放在你喜欢的版本控制库里即可。

部署

Octopress 为我们提供了三种部署方式:GitHub Pages,Heroku,Rsync,在我看来,其实就两种:免费和收费。GitHub Pages 和 Heroku 都是免费使用,Rsync 则需要你拥有自己的虚拟主机。我选择了 Heroku,毕竟 GitHub Pages 本意是用来放项目介绍页面的,结果被强大的 hacker 们发掘来作为博客了⋯⋯ 我现在使用的是 GitHub Pages。

Continue?

开始享受写作的乐趣吧~

SPDY 简介

今天在看 CTF write-up 时发现有人提到 SPDY 这样一个东西,貌似跟 Chrome 项目有关,于是在 Geek 原始冲动的驱使下了解了一下。

首先 SPDY 是一个应用层协议,它被创造出来的唯一目的就是让 Web 更快,更快,还是更快。Google 这家公司似乎很喜欢「快」这个东西,Chrome 从诞生到现在每次几乎必定宣传自己有多么得快,搞得大家已经产生了某种心理暗示。SPDY 诞生于 2009 年,其实这是对外公开发布的时间,开始研究的时间应该更早。众所周知,如今的 Web 是通过 HTTP 协议和 TCP 协议进行传输,但种种因素导致 HTTP 传输变得很慢:

  • 每一个 TCP 连接一次只能发一个 HTTP 请求,这个估计是 HTTP 协议的最大弊端。想象一下如今的网站已经包含大量的图片、CSS、JS 需要加载,如果一个请求一个请求地发,那肯定会慢死,所以浏览器通常都是通过建立多个连接来回避这个问题,但毕竟治标不治本。
  • 只能由客户端主动发起 HTTP 请求,即时有时服务器知道还需要回复其它资源,它也只能等客户端先发起再回复。服务器真可怜,太被动了。
  • HTTP 头没有压缩,而且 HTTP 头也有一些冗余信息,比如 User-Agent 就没有必要每次都发来发去,太浪费带宽了。
  • 数据压缩是可选的,Google 认为必须强制要求。

既然 HTTP 有这么多缺点,那应该不止 Google 自己想要解决,其实是有的,本着不重复造轮子的原则 Google 列举了现有的一些改进方案:

  • HTTP pipelining:以流水线的形式传输请求和数据,这里吐槽一下,以前在公司时 Facebook 的某牛来介绍时谈到了他们开发的 BigPipe,思想也是流水线,同样也是为了优化 Web 性能,不知道他们是不是借鉴了 HTTP pipelining,:)
  • SCTP:用于替代 TCP 的传输层协议,提供了 multiplexed streams(多路复用流)和 stream-aware congestion control(流感知拥塞控制)
  • SST:同样用于替代 TCP 协议(TCP 同学真是众矢之的⋯⋯),也可以运行在 UDP 协议之上。
  • MUXSMUX:运行在传输层和应用层之间的中间协议,同样提供了复用流。

但是 Google 同学觉得以上这些都还不够,它要追求更大程度的性能提升。考虑到 TCP 现在应用还很广泛,想替代也不是一天两天的事情,但 HTTP 就不一样了,它是应用层的!所以说有自家的浏览器就是好办,发明个应用层协议马上就可以上线。SPDY 在刚出来的时候 Google 还在说这并不是用来替代 HTTP 协议的,它只是一个中间协议,但看看最新的协议文档里面已经将 SPDY 分为了两层,其中一层被描述为 HTTP-like,大有取代 HTTP 的意图(Google 最近的一篇文章已经直呼 SPDY 为「a replacement for HTTP」)。可以想到 Google 已经将提议提交给 IETF,也许未来的某一天我们就不再使用 HTTP 协议了。SPDY 主要有以下一些特性:

  • multiplexed streams,一个 TCP 连接将支持无限的并发 HTTP 请求
  • 请求优先级,因为现在支持并发请求,就必须得为每一个请求设置一定的优先级
  • 压缩 HTTP 头,去掉多余的头信息
  • 全部请求都是通过 SSL 加密,Google 认为安全网络连接必定是未来的发展方向,即使加密会微微增加一些传输时间
  • Web 服务器将能够主动发起通信,也就是 server push
  • 还有一个类似的叫 server hint,不同于 server push 的是它仅仅向客户端发送一个 suggest,提示客户端需要发送一个 HTTP 请求

这些改进到底能有多大提升?Google 给出的数据是 39%~55%,在丢包严重或高延迟环境下,SPDY 表现更加出色。

要支持 SPDY,除了客户端必须支持外,还要有相应的 Web 服务器。现在已经有 JavaApache modulePythonRubynode.js 等各种实现。

最后,如果你正在使用 Chrome 浏览器,并且访问 Google 的网站,那你已经开始使用 SPDY 了,输入 chrome://net-internals/#spdy 还可以了解更加详细的信息。