我有写大量的代码, 但我想要是能更快更好的读代码的能力也很重要. 我和 @ranguard 有一起共事的殊荣, 我发现他象一个读代码的猎豹, 非常让人羡慕. 所以我现在开始分析各种 CPAN 的模块源代码来进行练习. 先从 Plack 开始.
Plack
Plack 的文档中介绍自己只是一个 PSGI 的工具集 PSGI (the Perl Server Gateway Interface).
Plack 在 CPAN 上最高的版本是 2009-10-13 号的 0.9000. 头两年每周会有几次 releases. 在 2012 年基本上每周仍然有一次更新. 在 2013 年好象慢下来, 基本一个月才会更新一次.
代码本身写得非常的简洁和非常注重细节. 基本没有多少注释. 基本 71 个文件中有 43 个文件小于 3 个注释. 当然, 代码本身写得很优美, 就使得不需要多少注释, 现在有很多优秀的 POD 也是这样, 都是代码是自解释的.
这些代码使用了大量的回调 ( 代码引用 ). 这也就是说它是重度事件驱动的. 这对于现在流行的 Web 服务器事件驱动模型, 这是合理的. 对大家来讲, 这很有 JavaScript 的味道. 例如 Plack::Util::foreach 就很象 jQuery.each() 通过遍历数组调用每个元素的代码引用.
Plack::Util::foreach([1,2,3], sub{ printshift}); # prints "123"背景了解
去读读最重要的 PSGI spec 非常重要. Plack 是为了处理 PSGI 的实现. 这个写得非常好并很清晰, 但是可能有点枯燥, 因为缺乏上下文. 不过这个对于读代码本身非常有帮助.
开始
安装和取得代码
~/code ⚡ git clone git@github.com:plack/Plack.git
~/code ⚡ cd Plack这个项目的依赖列表是在 cpanfile 中. 如果读代码和执行测试最好是使用 Carton 来安装和执行.
~/code/Plack ⚡ carton
~/code/Plack ⚡ prove-rltPlack 有些什么人为它工作?
让我们了解一下谁参与了该项目:
~/code/Plack ⚡ git shortlog --summary --numbered | head
1567 Tatsuhiko Miyagawa
70 Kazuho Oku
68 Tokuhiro Matsuno
20 Daisuke Murase
20 Jesse Luehrs
19 yappo
16 Karen Etheridge
16 Mark Stosberg
12 hiratara
11 Stevan Little它有多大?
~/code/Plack ⚡ tree lib | tail-1
17 directories, 70 files分别有什么语言
~/code/Plack ⚡ cloc . 2>/dev/null | tail -13
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
Perl 82 2889 3341 5309
Bourne Shell 8 55 138 251
YAML 1 0 0 19
HTML 3 2 0 14
Python 1 2 1 6
Javascript 1 0 0 1
CSS 1 0 0 1
-------------------------------------------------------------------------------
SUM: 97 2948 3480 5601
-------------------------------------------------------------------------------
我们看看 lib 目标结构?
我们可能很难从所有的 71 个文件中去了解怎么样工作的, 但我们可以扫一眼看看, 这样相当有助于我们组织和理解代码. 这个看起来有 3 个大类.
1 - 模块的加载和 PSGI 服务器的运行
plackup
Plack::Handler
Plack::Handler::*
Plack::Loader
Plack::Loader::*
Plack::Runner
2 - 用于构建 PSGI apps 的模块.
Plack::App::*
Plack::Builder
Plack::Component
Plack::Middleware
Plack::Middleware::*
3 - 测试用的模块
Plack::Test
Plack::Test::*
Plack::HTTP::Message::PSGI
Plack::LWPish
我们来接着看这些 lib 目录下所有的东西, 我在后面都加了一个简短的描述, 这认为这还是比较有参考意义的. 下面的数字对应上面所列出来的类别.
~/code/Plack ⚡ tree lib
lib
├── HTTP
│ ├── Message
│ │ └── PSGI.pm # (3) 转换 HTTP::Request 变成 PSGI 环境变量 env 的哈希结构
│ └── Server
│ └── PSGI.pm # (1) PSGI web 服务器所需要的引用; 没有依赖; 通常不用于生产环境
├── Plack
│ ├── App # (2) 这个 lib 继承自 Plack::Component; 这是 PSGI web apps
│ │ ├── Cascade.pm # 为每个请求, 尝试调用一些 PSGI 的 app 直到响应正常
│ │ ├── CGIBin.pm # 目录中有多个 CGI 的脚本时, 通过 WrapCGI 来创建多个 CGI 的 PSGI 的应用
│ │ ├── Directory.pm # 服务一个目录下文件
│ │ ├── File.pm # 服务普通文件
│ │ ├── PSGIBin.pm # 从目录中的 .psgi 文件来创建 PSGI apps
│ │ ├── URLMap.pm # 映射 url 到 PSGI app
│ │ └── WrapCGI.pm # 从 CGI 的脚本来创建单个 PSGI app
│ ├── Builder.pm # (2) 使用 DSL 风格来构建 Plack 的中间件
│ ├── Component.pm # (2) 一个可选的工具, 用于构建 PSGI web apps
│ ├── Handler
│ │ ├── Apache1.pm
│ │ ├── Apache2
│ │ │ └── Registry.pm
│ │ ├── Apache2.pm
│ │ ├── CGI.pm
│ │ ├── FCGI.pm
│ │ ├── HTTP
│ │ │ └── Server
│ │ │ └── PSGI.pm # HTTP::Server::PSGI 中的 Plack::Handler
│ │ └── Standalone.pm # Plack::Handler::HTTP::Server::PSGI 的别名
│ ├── Handler.pm # (1) 用于实例化和运行 PSGI 兼容的服务
│ ├── HTTPParser
│ │ └── PP.pm # 通过 XS 解析 HTTP headers
│ ├── HTTPParser.pm # (1) 解析 HTTP headers; used by HTTP::Server::PSGI
│ ├── Loader
│ │ ├── Delayed.pm # 延迟编译 PSGI app 直到第一个请求到达
│ │ ├── Restarter.pm # 当检查到本地文件变化时, 重起服务
│ │ └── Shotgun.pm # 为每个请求重新编译 PSGI app
│ ├── Loader.pm # (1) 载入 PSGI 兼容的 web 服务
│ ├── LWPish.pm # (3) 轻量版本的 LWP 用于测试的
│ ├── Middleware
│ │ ├── AccessLog
│ │ │ └── Timed.pm # 写 access logs 但可以处理fake File::IO 的内容
│ │ ├── AccessLog.pm # 写 access logs
│ │ ├── Auth
│ │ │ └── Basic.pm # Basic authentication
│ │ ├── BufferedStreaming.pm # 打开 streaming 的服务
│ │ ├── Chunked.pm # 用于实现 HTTP/1.1 的部分 - chunked HTTP transfer encoding
│ │ ├── ConditionalGET.pm # 用于实现 HTTP/1.1 的部分 - Conditional GET
│ │ ├── Conditional.pm # 根据指定的条件来运行指定的中间件
│ │ ├── ContentLength.pm # 如果可以,就添加一个 Content-Length header
│ │ ├── ContentMD5.pm # 设置 Content-MD5 header 当 body 是一个数组引用的时候
│ │ ├── ErrorDocument.pm # 不同的 HTTP 错误的时候, 显示不同的错误文档
│ │ ├── Head.pm # 如果是 HEAD 请求, 会删除所有的响应 body
│ │ ├── HTTPExceptions.pm # 当出现 HTTP::Exceptions 的时候, 重定向到错误页面
│ │ ├── IIS6ScriptNameFix.pm # Fix for IIS
│ │ ├── IIS7KeepAliveFix.pm # Fix for IIS
│ │ ├── JSONP.pm # 如果指定了 callback 的参数, 就给 JSON 的响应转换成 JSONP 的响应
│ │ ├── LighttpdScriptNameFix.pm # Fix for Lighttpd
│ │ ├── Lint.pm # 检查 input/output 是否兼容 w/PSGI spec
│ │ ├── Log4perl.pm # Log with Log::Log4Perl
│ │ ├── LogDispatch.pm # Log with Log::Dispatch
│ │ ├── NullLogger.pm # 清除日志处理函数
│ │ ├── RearrangeHeaders.pm # Fix for very old MSIE and broken HTTP proxy servers
│ │ ├── Recursive.pm # 允许应用程序将请求转发到不同的路径(url)
│ │ ├── Refresh.pm # 类似 Plack::Loader::Restarter 但有少量的不一样
│ │ ├── Runtime.pm # 设置 'X-Runtime' HTTP response header = app's response time
│ │ ├── SimpleContentFilter.pm # 过滤响应的内容
│ │ ├── SimpleLogger.pm # 日志信息
│ │ ├── StackTrace.pm # 显示 PSGI 应用 die 的堆栈
│ │ ├── Static.pm # 服务静态文件
│ │ ├── XFramework.pm # 增加 X-Framework HTTP response header
│ │ └── XSendfile.pm # 增加 X-Sendfile HTTP response header
│ ├── Middleware.pm # (2) 封装 PSGI apps; 能修改进入的请求 / 输出的响应
│ ├── MIME.pm # 全部的 MIME 类型(mostly)
│ ├── Request
│ │ └── Upload.pm # Plack::Request 有关文件上传的一个子类
│ ├── Request.pm # (2) 低级的 request 对象, 用于中间件和 web 应用
│ ├── Response.pm # (2) 低级的 Response 对象, 用于中间件和 web 应用
│ ├── Runner.pm # (1) plackup 的核心 -- 用于 Plack::Loader 和 Plack::Handler
│ ├── TempBuffer.pm # 用于向后兼容, 存储数据到内存, 如果文件很大会自动存储到文件;
│ ├── Test
│ │ ├── MockHTTP.pm # 不使用 server 来用于测试 PSGI app (最快的)
│ │ ├── Server.pm # 使用简单的 server 来测试 PSGI app (比较快的)
│ │ └── Suite.pm # 确保 Web 服务器符合 PSGI spec
│ ├── Test.pm # (3) test objects 的工厂
│ ├── Util
│ │ └── Accessor.pm # 轻量的 Class::Accessor 用于向后兼容
│ └── Util.pm # 混杂的但整个代码库使用的重要工具
└── Plack.pm # 这没有代码 -- 只有 POD 文件各版本号