书接上文。
在 Clozure CL 上启动 CL-HTTP
CL-HTTP 在
Clozure CL (CCL) 上跑得比 SBCL 更好一些,因为 CCL 的多线程 API 特性更加丰富,与 CL-HTTP 的可移植兼容层的吻合度也比较高。CCL 是我最喜爱的开源 Common Lisp 实现。它由商业公司维护,性能稳定可靠,并且在所有支持的 OS/CPU 组合上都有多线程支持以及 32/64 位版本 (ARM 除外,只有 32 位),在 Mac OS X 上甚至还有一个 IDE。目前最后发布的 CCL 版本 1.6 支持下列五种操作系统:
- Mac OS X (PowerPC and x86)
- Linux (ARM, PowerPC and x86)
- FreeBSD (x86)
- Solaris (x86)
- Microsoft Windows (x86)
安装 Clozure CL 的过程非常简单,首先按照用户所在的平台将对应的源代码和二进制文件一次性地从 SVN 上 checkout 下来。例如,对于 Mac OS X 上的版本 1.6,相应的 SVN URL 是:
http://svn.clozure.com/publicsvn/openmcl/release/1.6/darwinx86/ccl
只需调整 URL 中的版本号部分即可获得 CCL 的其他版本 (目前的可用范围是 1.2 - 1.7)。对于版本 1.6 来说,平台目录除 darwinx86 以外还包括下列平台组合:
- darwinppc
- freebsdx86
- linuxarm
- linuxppc
- linuxx86
- solarisx86
- windows
Clozure CL 项目以一种奇特的方式将所有文件放在 SVN 里管理,所有二进制目录里使用 svn:external 属性链接到相应的源代码目录和预处理头文件 (cdb) 目录上。由于 CCL 是完全自举架构的,即新版本的 CCL 二进制文件是从老版本的 CCL 加载新的源代码以后生成的,因此二进制文件是作为构建 CCL 新版本的原材料来管理的。另外每当一些产生一些重要的源代码修改时,自举过程可能短暂地需要人工干预,这些工作通常是由 CCL 核心开发者来处理并提交修复后的二进制文件。
将 CCL 随便安装在任何目录里(所谓安装也就是找个地方就地 svn checkout 上述 URL),然后要做的事情包括以下两点:
- 定义一个环境变量 CCL_DEFAULT_DIRECTORY,使其内容等于 CCL 的安装路径。
- Windows 上需要设置环境变量,将 %CCL_DEFAULT_DIRECTORY% 写入 PATH 环境变量中;其他平台上推荐将 $CCL_DEFAULT_DIRECTORY/scripts 目录下的 ccl 和 ccl64 两个脚本软链接到 PATH 环境变量所包含的某个目录里,例如 /usr/local/bin。
CCL 的启动方法是,Windows 下打开一个命令提示符窗口,然后根据自己系统的实际情况执行 wx86cl 或者 wx86cl64;其他平台下根据实际情况执行 ccl 或 ccl64。如果顺利启动的话应该可以看到类似下面的提示:
binghe@binghe-mac:~$ ccl64
Welcome to Clozure Common Lisp Version 1.6-r14820 (DarwinX8664)!
关于 Clozure CL 使用方法的其他问题,尤其是当源代码出现更新以后的重编译方法,请参阅
官方文档。
在 Clozure CL 上启动 CL-HTTP 的方法跟在 SBCL 上是完全一样的。即首先加载 contrib/kpoeck/port-template/load.lisp,然后再用 (compile-all) 编译所有源代码,(http::start-examples) 启动示例站点(浏览器中的效果如本文插图所示)。CCL 的编译速度非常快。
不过需要注意的是,CL-HTTP 的 SBCL 和 CCL 移植是通过 kpoeck 的 port-template 框架进行的,这个框架的缺点在于 HTTP 请求的多线程处理相关代码还很不成熟。当 HTTP 服务器启动以后,Lisp 环境中只有一个 HTTP 监听器线程:
* (sb-thread:list-all-threads)
(#<SB-THREAD:THREAD "Stupid cl-http-accept-thread" RUNNING {1004BABBD1}>
#<SB-THREAD:THREAD "initial thread" RUNNING {10048393C1}>)
对于每个新的 HTTP 请求,CL-HTTP 都会临时派生一个新线程处理,一旦处理完毕线程就会退出;对于多并发的请求来说性能成问题。因此 CL-HTTP 的 SBCL 和 CCL 移植目前还无法胜任产品级的应用需求。不过一旦学会了 CL-HTTP 的使用方法,以后可以进而使用 LispWorks、MCL、SCL 甚至 CMUCL 等平台运行产品级的 CL-HTTP 应用。
注意到示例站点的 URL 为 http://localhost:8000。如果想要在其他 IP 地址或者端口上运行示例站点,可以带参数调用 HTTP::START-EXAMPLES 函数,语法是:
http::start-examples &key host port (type :stupid-multi)
其中 host 是字符串格式的主机名或 IP 地址,port 是数值格式的端口,type 是一个关键字参数,除了 :stupid-multi 以外另一个可用的值是 :single,这导致 CL-HTTP 可以运行在单线程模式下,每次处理一个请求,并且上述函数将不会返回。单线程模式易于调试,甚至有可能把 CL-HTTP 跑在不支持多线程的 SBCL 版本上。
(有资料表明,很多 Lisp 程序员将 CL-HTTP 与 Hunchentoot 一起混用,Hunchentoot 用来提供多线程处理和 HTTP 协议的实现,CL-HTTP 负责生成动态 HTML/XHTML 代码。本系列文章的后续部分会简要介绍 CL-HTTP 的 HTML 生成宏。)
保存 SBCL 和 CCL 下的编译成果
在前述的 CL-HTTP 加载过程中,每次执行 (COMPILE-ALL) 时都要重新编译 CL-HTTP 的所有源代码。通常,使用 ASDF 或者 MK:DEFSYSTEM 的 Lisp 程序是可以做到按需编译的,但 CL-HTTP 的 port-template 似乎有意关闭了按需编译以确保 CL-HTTP 代码的加载过程具有某种确定性。对于 CL-HTTP 这种规模比较大并且代码相对稳定的项目来说,每次加载不同的用户站点代码时都要重新编译整个 CL-HTTP 是不值得的(我最近在基于 MIPSel 的嵌入式系统上使用 SBCL 1.0.28 完整编译 CL-HTTP 花掉了 1 小时 36 分,而在我最好的电脑上这个过程只需 40 秒),因此推荐的做法是在着手开发用户站点之前,先将 CL-HTTP 中除了示例站点以外的代码以二进制形式导出到一个 core (或 image,不同的 Lisp 平台有各自推荐的扩展名,导出的文件不能跨平台使用) 文件中,从而实现快速加载。
SBCL 下的做法如下:
首先进入 port-template 的根目录(也就是 CL-HTTP 源代码的 contrib/kpoeck/port-template 目录),然后不加载用户初始化文件启动 SBCL,然后依次输入红色标识的三个命令:
binghe@binghe-mac:~/Lisp/cl-http/contrib/kpoeck/port-template$ sbcl --no-userinit
This is SBCL 1.0.49.78-0bce932, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.
SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses. See the CREDITS and COPYING files in the
distribution for more information.
* (load "load.lisp")
* (compile-all)
* (save-lisp-and-die "cl-http.core")
这样就在当前目录下得到了一个 cl-http.core 文件,以后可以使用命令行
sbcl --core cl-http.core 从该文件启动 SBCL (如果从其他目录启动 SBCL 则需要提供该文件的全路径)。事实上 SBCL 在不使用 --core 命令行参数启动时也会加载一个默认的 sbcl.core 文件,其中含有 Common Lisp 语言的实现和一些 SBCL 自己的语言扩展。从某种意义上来说,一个 Lisp 程序本质上就是对 Lisp 语言的扩展,用户代码和语言本身的实现代码在本质上是一样的,在 core 文件中的地位也是均等的。当然,诸如 car 和 cdr 这些底层 Lisp 函数最终上会被映射到 C 语言编写的底层 runtime 函数上。对 SBCL 的内部结构感兴趣的读者可以参考
SBCL Internals wiki 站点。关于
sb-ext:save-lisp-and-die 的详细说明请参见
SBCL 官方手册。
Clozure CL 下的过程是类似的,不过 CCL 将 core 文件称为 image 文件。导出方法是类似的,首先不加载用户初始化文件启动 CCL,然后执行同样的前两个命令编译 CL-HTTP,最后用一个不同的命令导出 image 文件:
binghe@binghe-mac:~/Lisp/cl-http/contrib/kpoeck/port-template$ ccl64 -n
Welcome to Clozure Common Lisp Version 1.6-r14820 (DarwinX8664)!
? (compile-all)
? (save-application "cl-http.image")
最后得到的 cl-http.image 文件可以使用 CCL 命令行
-I cl-http.core 来加载,注意 32 位和 64 位的 image 不能通用。关于
CCL:SAVE-APPLICATION 命令的其他细节请参见
CCL 官方手册。
一旦以带有 CL-HTTP 的 core 或 image 文件启动了 SBCL 或 CCL,仍然可以继续使用 (HTTP::START-EXAMPLES) 来启动示例站点。这样可以大幅节省启动时间,并且为后面开发用户站点打下了一个良好的基础。
(未完待续)