扶凯

取势 明道 优术

作者为 扶 凯 发表

我有写大量的代码, 但我想要是能更快更好的读代码的能力也很重要. 我和 @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 { print shift }); # 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 -rl t

Plack 有些什么人为它工作?

让我们了解一下谁参与了该项目:

~/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 – 测试用的模块

我们来接着看这些 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 文件各版本号


来了就留个评论吧! 1个评论