CherryPy Essentials ||已挖坑 || By andelf || 完成度7/27 || 目前进度缓慢 || 需要校对 || 招募义工

CherryPy Essentials

Translated By Feather.et.ELF ([email protected])

2008/09/30

1. 目录

  1. 前言
  2. CherryPy 介绍

    1. 概述
    2. CherryPy 历史

    3. 社区
    4. CherryPy 项目规模

    5. CherryPy 之外

    6. 全书概览
    7. 小结
  3. 下载安装 CherryPy

    1. 需求
    2. 概述
    3. 从 Tarball 安装
    4. 通过 Easy Install 安装
    5. 从 Subversion 安装
    6. 测试安装
    7. 随时更新 CherryPy

    8. 小结
  4. CherryPy 概览

    1. 词汇表
    2. 基本例子
    3. 内建 HTTP 服务器
    4. 内部引擎
    5. 配置
    6. 对象发布引擎
      • 自动导入特性
      • 缓存模块
      • Coverage 模块
      • Encoding/Decoding 模块
      • HTTP 模块
      • Httpauth 模块
      • Profiler 模块
      • Sessions 模块
      • Static 模块
      • Tidy 模块
      • Wsgiapp 模块
      • XML-RPC 模块
    7. 工具
    8. 错误和异常处理
    9. 小结
  5. 深入了解 CherryPy

    1. HTTP Compliance
    2. 多重 HTTP 服务器
    3. 多线程应用服务器
    4. URI Dispaching
      • HTTP 方法 Dispacher
      • Routes Dispacher
      • 虚拟主机 Dispacher
    5. Hook 到 CherryPy 核心引擎

    6. CherryPy 工具箱

      • 基本认证工具
      • 缓存工具
      • 解码工具
      • 摘要认证工具
      • 编码工具
      • 错误重定向工具
      • Etag 工具
      • Gzip 工具
      • 忽略 Headers 工具
      • 记录 Headers 工具
      • 记录跟踪返回工具
      • 代理工具
      • 参考工具
      • 请求头工具
      • Trailing Slash 工具
      • XML-RPC 工具
      • 工具箱
      • 创建新的工具
    7. 静态资源服务
      • 使用 Staticfile 工具提供单个文件
      • 使用 Staticdir 工具提供整个目录
      • 绕开 Static 工具提供静态内容
    8. WSGI 支持
      • 使用 CherryPy 支持 WSGI 应用

      • 使用第三方服务器支持 CherryPy WSGI 应用

    9. 小结
  6. 一个图片 Blog 应用
    1. 图片 Blog 应用
    2. 图片 Blog 实体
    3. 词汇表
    4. DBMS 概览
      • 关系数据库管理系统(RDBMS)
      • 面向对象数据库管理系统(OODBMS)
      • XML 数据库管理系统(XMLDBMS)
    5. 对象关系映射
      • Python 对象关系映射
    6. 图片 Blog 实体模型的建立
      • 映射实体
      • 单位和单位属性
      • 联合单位
      • 沙箱接口
    7. 查询单位
    8. 扩展数据访问层
    9. 小结
  7. Web 服务
    1. 传统 Web 开发
      • 分工
    2. REST
    3. 统一资源定位符
    4. HTTP 方法
    5. 把它们放在一起
    6. CherryPy 的 REST 接口

    7. Atom 发布协议
    8. Atom XML 文档格式
    9. APP 实现
    10. 小结
  8. 表现层
    1. HTML
    2. XML
    3. XHTML
    4. CSS
    5. DHTML
    6. 模板
    7. Kid 模板引擎
      • 概览
      • Kid 属性
        • 基于 XML 的模板语言
        • 变量替换
        • 条件语句
        • 循环机制
        • 扩展性
        • 其他属性
    8. 图片 Blog 设计表现
      • 面向用户代理
      • 工具
      • 全局设计目标
      • 设计目录布局
      • CherryPy - 封装模板渲染过程

    9. 图片 Blog 设计细节
      • 基本结构
    10. Mochikit
    11. 开发图片 Blog 设计
      • HTML 代码
      • 添加链接
      • 处理最终用户行为
      • 修改模板
      • 修改 CSS
      • 可扩展性
    12. 小结
  9. Ajax
    1. 富客户端应用的流行
    2. Ajax
      • Ajax - 优点和缺点
      • 在后台: XMLHttpRequest
        • 执行 GET 请求
        • 执行内容协商 GET 请求
        • 执行 POST 请求
        • 执行 PUT, HEAD, 或 DELETE 请求
        • Cookies
        • 使用摘要或基本方案进行认证
    3. JSON
    4. 在我们的应用里使用 Ajax
      • 定义所需名称空间
      • 实现名称空间
      • 给类添加方法
      • 创建新相册的方法
      • 更新已有相册的方法
      • 删除已有相册的方法
    5. 小结
  10. 测试
    1. 为什么要测试
    2. 计划测试
    3. 常见测试方法
    4. 单元测试
      • unittest
      • doctest
    5. 单元测试 Web 应用
    6. 功能测试
      • 测试下的应用程序
        • Selenium Core
        • Selenium IDE
        • Selenium Remote Control
    7. 小结
  11. 部署应用
    1. 配置
      • CherryPy - Web 及引擎配置系统

      • 图片 Blog 应用配置系统
    2. 部署
      • Apache 下的 mod_rewrite 模块
      • Lighttpd 下的 mod_proxy 模块
      • Apache 下的 mod_python 模块
      • mod_python 与 WSGI 应用
    3. SSL
      • 创建认证和私钥
        • 使用 CherryPy 的 SSL 支持

        • 使用 Lighttpd 的 SSL 支持
        • 使用 Apache 的 mod_ssl 支持
    4. 小结
  12. 索引


2. 前言

在过去几年里, 我们经历了 Internet 大爆发, 许多编程语言都开始提供大量的 web 开发工具, 库, 以及框架.

Python 编程语言同样提供了这些环境, 例如 Zope 和 Twisted , 它们中的很多 只是很小的社区. 这时, CherryPy 出现了, 它的作者...

2.1. 本书包含内容

2.2. 你需要准备的东西

本书假定你已经安装好如下程序包.

2.3. 本书的读者

本书原则上面向 web 开发人员. ...

2.4. 格式约定

本书中, 你会发现很多文本样式, 分别用于提供不同的信息. 这里做一简要说明.

代码有三种样式. 文本中的代码单词例如: A newer and more common way of deploying a package is to use the easy_install command to install eggs.

代码块例如:

blody
{
  background-color: #663;
  clor: #fff;
}
p
{
  text-align: center;
}

命令行输入使用如下格式:

python ez_setup.py

术语重要单词 使用黑体字显示. 屏幕上的字, 例如在菜单或是 对话框中, 使用: 下一步, 单击 All 按钮, 运行这些测试.

WARN 警告和重要评注将出现在这里.

TIP 各种 Tips 和小技巧将出现在这里.

2.5. 读者反馈

我们欢迎来自读者的反馈信息. 请让我们知道你对这本书的看法, 无论喜欢与否. 读者反馈信息对我们的进步来说是很重要的, 同时也可以为大家提供更好的服务.

一般反馈信息请发送邮件到 [[email protected]] , 记得在邮件里包含本书 的名字.

如果你需要我们出版某类图书, 请在 www.packtpub.com 站点上的 SUGGEST A TITLE 给我们发送短信, 或是发送邮件到 [[email protected]] .

如果你想就某一擅长的话题写作书籍或是为图书贡献的话, 请参考 www.packtpub.com/authors 上的作者指导.

2.6. 客户支持

现在, 你是一本 Packt 图书的拥有者, 这里有些相关信息帮助你利用好这本书.

2.7. 下载本书的范例代码

请访问 http://www.packtpub.com/support , 从列表中选择本书的名字, 下载 本书的范例代码以及其他资料.

所下载文件包含使用说明.

2.8. 勘误

尽管我们尽力确保本书内容的正确性, 但是错误是难免的. 如果你在我们的书里 发现错误 - 正文或是代码 - 请报告给我们, 不胜感谢. 这样会帮助其他读者使 他们不会陷入疑惑中, 同时也有助于我们改进后继版本. 如果你发现任何错误, 请汇报至 http://www.packtpub.com/support , 选择书名, 点击 Submit Errata 链接, 输入你所发现错误的细节内容. 一旦确认, 你所提交 的勘误将被加入到现有勘误列表中. (访问该站点你可以获得现有勘误列表).

2.9. 问题

如果你有关于本书的某方面的问题, 你可以联系 [[email protected]] , 我 们会尽力回答你的信件.


3. 1. 介绍 CherryPy

万维网 (World Wide Web) 的使用呈指数级增长, 已经成为我们生活中的一个重 要组成. 从开发者的观点来看, Web 提供了大量的机会和乐趣. 然而, 面向 Web 的技术太过繁杂, 很难决定使用哪个. 本书的目标是展示其中的一种, CherryPy, 一个 Python web 应用库.

本章将介绍 CherryPy 的特性和优点, 首先概括 CherryPy 的历史, 然后介绍它 成功的最大原因 - 友好的社区. 最后回顾下 CherryPy 革命的关键原则.

3.1. 1.1. 概述

CherryPy 为 Python 开发者提供了一个 HTTP 协议的友好接口. HTTP 是 World Wide Web 的支柱. Web 应用在过去几年里快速增长. 随着爆发而来的是各种编 程语言中大量的工具箱, 库, 以及框架. 所有的这些都是为了减少 web 开发人 员的劳动. 在这样的背景下, CherryPy 出现了, 它建立在 Python 的动态特性 上, 将 HTTP 协议绑定到一个符合 Python 习惯的 API 上.

Python 社区开发了大量的 web 库和框架, 以至于出现了个玩笑 "as much as a worry". 尽管其中只有少数吸引了社区的注意 (TurboGears, Django, Zope 等), 但是不可否认的是, 每个库或是框架都有对 Python 与 HTTP 接口的独特 处理思想, 或多或少都有一定的影响. CherryPy 的出现是因为那时候的 Remi Delon , 它的作者, 在已有选择里没有找到他想要的那个. 有很开发者被 CherryPy 的优点吸引, 加入社区为 CherryPy 的设计贡献力量. 今天, 这个项 目有者强大的社区基础, 在不同的环境下将它作为日常使用.

3.2. 1.2. CherryPy 历史

Remi Delon 在 2002 年六月发布了 CherryPy 的第一个版本. 这是一个成功的 Python web 库的起始点. Remi 是一个法国黑客, 他相信 Python 是 web 应用 开发的最终选择.

Remi 的方法吸引了很多开发者.

CherryPy 会把 URL 连同它的查询字符串映射到一个 Python 方法调用, 例如 http://somehost.net/echo?message=hello 会被映射到 echo(message='hello') .

在随后的两年里, 这个项目被社区支持, Remi 发布了一些改进版本.

在 2004 年六月, 开始了一场关于项目未来发展以及是否维持结构不变的讨论. 最主要问题之一是编译阶段, 这让 Python 开发者感觉不自然. 集体讨论和关于 一些项目原则的讨论最终发展成为对象发布引擎和过滤器的概念, 这很快成为 CherryPy 2 的一个核心组成.

最后, 在 2004 年十月, CherryPy 2 alpha 的第一个版本发布了, 作为这些核 心概念的证明. 随后六个月, 为了发布一个稳定版本(2005 年四月), 大家都投 入到的紧张工作里. CherryPy 2.0 是一个很大的成功; 不过, 大家也认识到它 的设计仍然可以改进, 它需要重构.

在进一步的社区反馈/讨论后, CherryPy 的 API 做了一些修改, 以使它的结构 更完美, 这便是随后在 2005 年十月发布的 CherryPy 2.1.0 . 该版本被流行的 TruboGears 项目采用 - 它是由一堆项目组成的一个 web mega-框架. 开发团队 在 2006 年四月发布了 CherryPy 2.2.0 .

CherryPy 作为核心组成出现在被广泛采用的 TruboGears 框架上意味着 CherryPy 的一些方面出现了越来越多问题. 例如它的 WSGI 支持, 缺乏最新的 文档, 还有它不是很出众的性能. 在重要的实际需求中, 不破坏向后兼容性去 扩展 CherryPy 2 是很困难的. 最终, 大家决定向 CherryPy 3 前进. CherryPy 于 2006 年十二月被发布.

3.3. 1.3. 社区

在过去几年里, 如果没有社区, CherryPy 就不会有现在这样的成功. Remi 清楚 地知道他并不想让 CherryPy 只是作为自己的私人项目存在, 而是成为一个社区 性质的项目.

CherryPy 一直都有一部分追随者, 但事实上 CherryPy 社区是在版本 2.0 开始 的. 在 2004 年十一月, Open and Free Technology Community (OFTC) 网络上注册了一个 IRC 频道, 开发者和用户可以通过它快速交换想 法, 或是报告错误. 这个频道慢慢吸引了越来越多的长期用户, 被广泛认为是一 个友好的地方. 除 IRC 频道外, 针对开发者和用户的邮件列表也被创建. 还有 一个 CherryPy 用户的 blog feed 聚合站点也建立了起来, 你可以访问 http://planet.cherrypy.org .

3.4. 1.4. CherryPy 项目优点

3.5. 1.5. 超越 CherryPy

在一开始, CherryPy 吸引了一小群用户, 但它的设计导致它不能被更多人接受 或是更广泛使用. 此外, 在那时, Python web 开发领域几乎完全被 Zope 平台 占据. CherryPy 2 发布时, 它的概念被社区热烈欢迎, 吸引了更多的用户在应 用中使用 CherryPy 或是基于它建立他们自己的包.

在 2005 年五月, Kevin Dangoor 发布了 TruboGears - 使用一堆开源产品建立 的一个 web 开发框架. 在框架里 Kevin 选择了 CherryPy 处理 HTTP 层, SQLObject 将对象映射到数据库, Kid 用做 XHTML 模板, MochiKit 作为客户端 处理. 这只是在另个 Python web 框架, Django 向社区开放的几个月之后. 这 两个项目都很快在 Python 社区流行起来, 由于它们之间的小小竞争, 使得流行 趋势更加迅速. TurboGears 的急速发展也推进了 CherryPy 知名度, 吸引了大 量的新用户.

这些新开发者为 CherryPy 增加了许多新特性, 同时也修复了一些缺点, 最终作 CherryPy 3 发布, 至本书完成时候库的最稳定版本.

CherryPy 的未来是明确的; Robert Brewer 所做的杰出工作使得库的处理速度 有了很大提高. TurboGears 的未来版本将会迁移到 CherryPy 3 上, 这会为开 发团队带来新的需要解决的问题, 也会让 CherryPy 继续前进.

3.6. 1.6. 全书概览

本书旨在介绍 CherryPy , 使你有信心在你自己的 web 应用中把它用好. 另外 我们也将展开对 web 应用设计的讨论. 首先本书将介绍 Python 社区使用的一 些获得安装 CherryPy 的常见方法, 例如使用 setup tools 和 easy_install. 同时你将大致了解 CherryPy 的主要概念, 逐渐明白这个库可以 做什么. 然后我们深入到库的一些特性, 例如 HTTP 能力, URI 调度器, 如何扩 展库, 以及它的 WSGI 支持.

此时, 你会对 CherryPy , 它的设计, 以及如何使用它有深入的了解. 然后, 本 书将通过一个简单的 blog 应用介绍各种技术和工具, 例如对象关系映射, web 服务, 以及 Ajax , 使你对 web 开发的各个层有一定的的了解.

我们将提出 blog 应用的目标和边界(x), 回顾 Python 数据库处理的情况, 解 释对象关系映像. 这里我们会展示 Python ORM 之一, Dejavu. 本书还会讨论 REST 和 Atom 发行协议(Atom Publishing Protocol), 它们提供了扩可展 web 应用的设计方法. 然后我们介绍 blog 应用的表现层, 它使用了 Kid 模板引擎 MockiKit JavaScript 库. 随后本书简单讨论了 Ajax 以及如何使你的应用 受益于它. 我们还会看到你的应用如何才能调用 web 服务. 然后, 本书会介绍 web 应用测试领域. 这来自于单元测试, 它通过其中的函数测试概念载入测试. 本书最后会介绍一些部署 web 应用到独立应用或是常见 web 服务器(例如 Apache, lighttpd)的方法.

虽然其中一些扩展章节并没有讨论 CherryPy 本身, 但是所有章节都以介绍 web 应用开发为核心. 本书教你使用 CherryPy , 你将有基础和动力继续学习更多相关话题.

3.7. 1.7. 小结

在读完这些介绍后, 你应该对本书一些了解. CherryPy 是一个简单且强大的 Python 库, 如果你寻找一个把 HTTP 协议中艰涩的部分封装在底层却同时不失 其优点的库, 那么 CherryPy 是你的正确选择. CherryPy 社区在过去的时间里 努力使这样一个产品成为可能; 本书会给你正确的指导, 让你利用好它.

4. 2. 下载安装 CherryPy

和大多开源项目类似, CherryPy 可以通过多种方法下载安装. 这里我们将介绍 下面三种方法:

每种方法都会为这个项目的用户带来不同的价值, 了解这点很重要.

当你读到这一章时, 你应该可以取得并部署 CherryPy , 同时知道如何在你自己 的软件里使用这些技术.

4.1. 2.1. 需求

本书通篇都假设你安装好了这些程序包:

同时我们假定你已经对 Python 本身有足够的了解, 本书将不会涵盖语言本身的 内容.

4.2. 2.2. 概述

安装 Python 模块或包通常只是很简单的过程. 首先我们讨论最常见的建立和安 装新的包的方法, 多亏有了 Python 2.0 中被加入的一个标准模块 distutils .

该模块提供了一个描述包结构的简洁接口, 包的需求关系, 编译规则都在其中. 对于用户来说, 通常只意味着输入命令:

python setup.py build
python setup.py install

第一条命令使用开发者指定的规则编译包, 在依存关系不满足的时候把错误汇报 给最终用户. 第二条命令将包安装到 Python 的默认第三方包/模块保存目录. 注意第二条命令会调用第一条命令来做快速检查, 确保在最后一次运行后没有任 何改动发生.

包和模块的默认位置在:

在 UNIX 或 Linux 下它可能因为你的 Python 安装位置不同而不同, 但是这些 目录路径几乎都是相似的. 导入模块时, Python 会在一系列目录中查找模块, 其中一些是默认的, 另一些则由用户提供, 直到它发现一个匹配的模块, 否则会 引发一个异常. 修改搜索列表可以通过定义 PYTHONPATH 环境变量或是直 接在代码中进行修改:

import sys
sys.path.append(path)

NOTE PYTHONPATH 环境变量是 Python 引擎启动时读取的环境变量之一.
它包含需要加入搜索列表的地三方模块和包的路径.

另个方法是创建一个和包同名的文件, 以 .pth 作为扩展名. 该文件内写入 包的完整路径.

虽然这很简洁, 但这个方法有它的局限性. 由于 sys.path 列表是有序的, 如果两个路径包含相同模块的不同版本, 那么你必须保证你的应用中导入的那个 可以被首先访问到. 这导致我们陷入包的版本问题.

假设你在全局安装的 Python 里安装了 CherryPy 2.2.1 , 它会被安装到 /usr/local/lib/site-packages/cherrypy 中. 然而, 路径并未包含任何版 本信息, 如果你必须安装 CherryPy 3.0.0 , 那么你必须覆盖已有安装的版本.

幸运的是, Python 社区提供了一个解决方法 - eggs . egg 是一个压缩的 文件夹, 连同包内的所有文件和子目录. 它的文件名含有包的版本细节.

NOTE egg 是可分发的压缩包, 它含有 Python 包或模块的相关信息(例如作者, 版本等).

例如, Python 2.4 编译 CherryPy 2.2.1 会得到文件 Cherrypy-2.2.1-py2.4.egg . egg 自身并不是很有用; 它的部署需要 easy_install , 一个包含处理 egg 逻辑的 Python 模块. 这意味着你可以在同 个目录下部署不同版本, 让 easy_install 去决定导入哪个版本.

下一章节我们将看看在通常情况下如何安装 CherryPy .

4.3. 2.3. 从 Tarball 安装

Tarball 是压缩的归档文件. 文件名来自于 UNIX 及关联系统的 tar 工具.

NOTE 最广泛使用的是 gzip 工具, tarball 的扩展名为 .tar.gz 或是 .tgz.

CherryPy 为每个发布都提供了一个 tarball, 无论是 alpha, beta, release condidate, 或是稳定版本. 你都可以从 http://download.cherrypy.org/ 获得.

CherryPy tarball 包含库的完整源代码.

从 tarball 安装 CherryPy 需要如下几步:

  1. http://download.cherrypy.org/ 下载你感兴趣的版本

  2. 找到文件存放位置, 解压缩之:
    • 在 Linux 下, 使用如下命令: BR

tar zxvf cherrypy-x.y.z.tgz
  1. 移动到解压出的文件夹, 输入如下命令编译 CherryPy BR

python setup.py build
  1. 最后, 使用如下命令创建全局安装(你可能需要管理员权限):

python setup.py install

NOTE 这些命令必须使用命令行输入. 在 Microsoft Windows 下你需要执行
DOS 命令行.

以上几步会为创建系统默认 Python 环境的全局安装. 有时候你可能并不想这样 或者不能这样. 例如你可能不只为一个版本的 Python 安装 CherryPy; 这时候 你需要在第 3 步或第 4 步指定正确的 Python 文件.

或者你可能不想在全局环境里安装, 如果在 UNIX 下你只需要把第 4 步替换为:

python setup.py install --home=~

这会将文件放置在 $HOME/lib/python, 其中 $HOME 是你的主目录.

在 Microsoft Windows, 下不存在 HOME, 你应该使用如下命令:

python setup.py install --prefix=c:\some\path

路径自身并不重要, 你可以使用符合你的环境的任意路径.

然后你必须确认 Python 在你需要导入模块的时候检查该路径. 最简单的方法是 设置 PYTHONPATH 环境变量:

export PYTHONPATH=~/lib/python

set PYTHONPATH=/some/path

TIP 注意这只会对你当前打开的命令行窗口有效, 在关闭窗口后会被丢弃. 
若想永久改变环境变量, 请通过 系统属性 | 高级 | 环境变量 设置.

setenv PYTHONPATH "/some/path"

PYTHONPATH 环境变量会在 Python 解释器启动的时候被读取, 自动加入它 的内部系统路径(sys.path).

4.4. 2.4. 通过 Easy Install 安装

easy_install 模块提供了增强的 Python 包和模块部署能力, 你可以在 Python Enterprise Application Kit(PEAK) 网站得到它. 从开发者的观点来看, 它提供了一个很简单的模块导入 API, 你可以轻松地导入 指定版本或是指定某一版本范围. 例如, 你可以通过如下代码导入在环境中可找 到的第一个版本号大于 2.2 的 CherryPy :

>>> from pkg_resources import require
>>> require("cherrypy>=2.2")
[Cherrypy 2.2.1 (/home/sylvain/lib/python/
Cherrypy-2.2.1-py2.4.egg)]

从用户的观点看, 它简化了下载, 编译, 部署 Python 的过程.

在安装 CherryPy 之前, 我们必须安装 easy_install 本身. 从 http://peak.telecommunity.com/dist/ez_setup.py 下载 ez_setup.py块, 然后使用有管理权限的用户执行:

python ez_setup.py

如果你没有管理员权限, 你可以使用 -install-dir (-d) 选项:

python ez_setup.py -install-dir=/some/path

确保 /some/path 在 Python 的系统路径中. 你可以把 PYTHONPATH置为该目录.

这样之后, 你的环境就支持 easy_install 了. 然后安装支持 easy_install 的 Python 产品, 你只需要如下命令:

easy_install product_name

easy_install 会在 Python Package Index(PyPI) 搜索指定产品. PyPI 是一个 Python 产品信息的主要仓库.

为了部署 CherryPy 的最新可用版本, 你需要执行以下命令:

easy_install cherrypy

easy_install 会下载 CherryPy , 编译, 然后安装到全局 Python 环境. 如果 你需要把它安装到指定位置, 请输入如下命令:

easy_install --install-dir=~ cherrypy

安装后, 你会在指定目录得到一个名称为 cherrypy.x.y.z-py2.x.egg 的文 件, 具体文件名取决于当前 CherryPy 的最新版本号.

4.5. 2.5. 从 Subversion 安装

Subversion 是一个很优秀的开源版本控制系统, 开发者可以通过它安全有效地 控制项目.

这样的系统的基本原则是注册一个资源, 然后记录它的每次更改, 这样开发者就 可以恢复到任意一个先前版本, 比较两个版本, etc. 资源可以是源代码文件, 二进制文件, 图像, 文档, 或是其他机器可读的格式.

Subversion 是自动的, 也就是说如果一个 commit 失败, 那么整个 commit 就 算是失败的. 另一方面说, 如果它成功的话, 整个项目的版本号会增加, 而不是 只涉及这个文件.

NOTE Subversion 一般被看做 CVS 的继承者, 它更友好.
当然, 还存在着其他版本管理系统, 例如 Monotone 或 Darcs.

在 Linux, 你可以从源代码安装 Subversion 或是使用软件包管理器. 我们来看 看如果从源代码安装的过程.

  1. http://subversion.tigris.org/ 获得最新的 tarball

  2. 然后在命令行里输入命令: BR

tar zxvf subversion-x.y.z.tar.gz
  1. 在解压后得到的目录地输入: ./configuure

  2. 使用 make 命令编译包.

  3. 你可能会用到 Python 的 Subversion 绑定: BR

make swig-py
  1. 全局安装 Subversion , 你需要在管理员权限下执行: BR

make install; make install-swig-py

大多情况下, 在 Linux 或是 UNIX 系统中, 通过命令行使用 Subversion 是很 简单的. 当然, 如果你喜欢图形界面的话, 我推荐你安装客户程序, 例如 eSvn 或是 kdesvn.

在 Microsoft Windows 下, 直接使用图形应用程序更简单, 例如 TortoiseSVN, 当然, 它也会安装 Subversion 客户端.

在以下情况推荐使用 Subversion 安装 CherryPy :

为了使用该项目的最新版本, 你需要检查仓库的 trunk 目录. 在 shell 下输入 下面的命令:

svn co http://svn.cherrypy.org/trunk cherrypy

TIP 在 Microsoft Windows 下, 你可以通过命令行或是直接通过
TortoiseSVN 的图形界面取得代码. 具体请参阅它的文档.

这将创建一个 cherrypy 目录, 然后将完整源代码下载到里面. 通常并不推 荐将开发中的版本部署到全局环境里; 你需要使用如下命令将 CherryPy 安装到 一个本地目录:

python setup.py install --home=~

python setup.py install --prefix=c:\some\path

然后, 记得设置好你的 PYTHONPATH 环境变量.

注意, 目录并不重要, 只要它能被 Pyhon 处理, 无论是使用环境变量还是通过 标准的 sys 模块, 都是可以的.

4.6. 2.6. 测试你的安装

无论你打算用什么方法把 CherryPy 安装部署到你的环境里, 你必须能够从 Python 命令行导入它:

>>> import cherrypy
>>> cherrypy.__version__
'3.0.0'

如果你没有把 CherryPy 安装到全局环境下, 别忘记设置环境变量, 否则你将得 到如下的错误信息:

>>> import cherrypy
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
ImportError: No module named cherrypy

4.7. 2.7. 更新CherryPy

更新或是升级 CherryPy 的方法取决于你是如何安装它的.

easy_install -U cherrypy

NOTE 在升级前记得备份你的文件, 这几乎总是正确的.

4.8. 2.8. 小结

我们在本章讨论了安装 CherryPy 的三种方法. 传统方法是使用包含 Python 包 的所有文件的归档安装, 执行归档文件里提供的 setup.py 文件. 更常见的 方法是使用 easy_install 安装 egg. 最后, 如果你想和 CherryPy 的最新 开发保持同步的话, 你可以从它的 Subversion 仓库获得包. 无论使用哪种方法, 你都可以得到一份可用的 CherryPy.

5. 3. CherryPy 概览

在第一章我们简单介绍了 CherryPy 的一些概念;现在我们深入了解下该项目 是如何设计和构造的. 首先我们将讨论一个简单的 CherryPy 例子. 然后讨论 CherryPy 核心, 对象发布引擎, 以及它是如何将 HTTP 协议封装到一个面向对 象的库里的. 下一步我们将核心钩子(hook), CherryPy 库, 以及工具机制. 然后我们会回顾 CherryPy 的错误和异常处理, 以及如何使用它.

学完本章后, 你将对 CherryPy 库有一个很好的了解; 在以后的学习中你可能需 要再回顾本章的内容.

5.1. 3.1. 词汇表

为了避免误解, 我们首先定义一些在本书会出现的关键词.

web 服务器
web 服务器是处理 HTTP 协议的接口. 它将传入的 HTTP 请求转换为传入应用服
务器的实体, 同时将来自应用服务器的信息转换并返回到 HTTP 应答中.
应用
应用是接受信息单位的软件, 应用各种逻辑后, 返回处理后的信息单位.
应用服务器
应用服务器是为一个或多个应用提供服务平台的部件.
web 应用服务器
web 应用服务器是 web 服务器和应用服务器的集合体.

CherryPy 是一个 web 应用服务器.

5.2. 3.2. 基本例子

为了展示 CherryPy 库, 我们将讨论一个很基本的 web 应用, 用户可以通过 HTML 表单在主页面上发表评论. 评论按照创建时间倒序排列. 我们将使用 session 对象来保存评论作者的名字.

每条评论都有一个 URI, 类似 /note/id 的格式.

创建一个名为 note.py 的空文件, 写入如下代码.

# -*- coding: utf-8 -*

# Python standard library imports
import os.path
import time

###############################################################
#The unique module to be imported to use cherrypy
###############################################################
import cherrypy

# CherryPy needs an absolute path when dealing with static data
_curdir = os.path.join(os.getcwd(), os.path.dirname(__file__))

# We will keep our notes into a global list
_notes = []

###############################################################
# A few HTML templates
###############################################################
_header = """
<html>
  <head>
    <title>Random notes</title>
    <link rel="stylesheet" type="text/css" href="/style.css"></link>
  </head>
  <body>
  <div class="container">"""

_footer = """
  </div>
  </body>
</html>"""

_note_form = """
  <div class="form">
  <form method="post" action="post" class="form">
    <input type="text" value="Your note here..." name="text" size="40"></input>
    <input type="submit" value="Add"></input>
  </form>
  </div>"""

_author_form = """
  <div class="form">
  <form method="post" action="set">
    <input type="text" name="name"></input>
    <input type="submit" value="Switch"></input>
  </form>
  </div>"""

_note_view = """
<br />
<div>
   %s
   <div class="info">%s - %s <a href="/note/%d">(%d)</a></div>
</div>"""

###############################################################
# Our only domain object (sometimes referred as to a Model)
###############################################################
class Note(object):
    def __init__(self, author, note):
        self.id = None
        self.author = author
        self.note = note
        self.timestamp = time.gmtime(time.time())

    def __str__(self):
        return self.note

###############################################################
# The main entry point of the Note application
###############################################################
class NoteApp:
    """
    The base application which will be hoested by CherryPy
    """
    # Here we tell CherryPy we will enable the session
    # from this level of the tree of published objects
    # as well as its sub-levels
    _cp_config = { 'tools.sessions.on': True }

    def _render_note(self, note):
        """Helper to render a note into HTML"""
        return _note_view % (note,
                             note.author,
                             time.strftime("%a, %d %b %Y %H:%M:%S",
                                           note.timestamp),
                             note.id,
                             note.id)
    
    @cherrypy.expose
    def index(self):
        # Retrieve the author stored in the current session
        # None if not defined
        author = cherrypy.session.get('author', None)
        
        page = [_header]
        
        if author:
            page.append("""
            <div><span>Hello %s, please leave us a note.
            <a href="author">Switch identity</a>.</span></div>""" % (author,))
            page.append(_note_form)
        else:
            page.append("""<div><a href="author">Set your identity</a></span></div>""")

        notes = _notes[:]
        notes.reverse()
        for note in notes:
            page.append(self._render_note(note))

        page.append(_footer)
        # Returns to the CherryPy server the page to render
        return page
    
    @cherrypy.expose
    def note(self, id):
        # Retrieve the note attached to the given id
        try:
            note = _notes[int(id)]
        except:
            # If the ID was not valid, let's tell the
            # client we did not find it
            raise cherrypy.NotFound
        return [_header, self._render_note(note), _footer]
    
    @cherrypy.expose
    def post(self, text):
        author = cherrypy.session.get('author', None)

        # Here if the author was not in the session
        # we redirect the client to the author form
        if not author:
            raise cherrypy.HTTPRedirect('/author')
            
        note = Note(author, text)
        _notes.append(note)
        note.id = _notes.index(note) 
        raise cherrypy.HTTPRedirect('/')

class Author(object):
    @cherrypy.expose
    def index(self):
        return [_header, _author_form, _footer]

    @cherrypy.expose
    def set(self, name):
        cherrypy.session['author'] = name
        return [_header, """
        Hi %s. You can now leave <a href="/" title="Home">notes</a>.
""" % (name,), _footer]

if __name__ == '__main__':
    # Define the global configuration settings of CherryPy
    global_conf = {
        'global': { 'autoreload.on': False,
                        'server.socket_host': 'localhost',
                        'server.socket_port': 8080,
                        'server.protocol_version': 'HTTP/1.1' 
                  }}
    application_conf = {
        '/style.css': {
            'tools.staticfile.on': True,
            'tools.staticfile.filename': os.path.join(_curdir, 'style.css'),
            }
        }
    # Update the global CherryPy configuration
    cherrypy.config.update(global_conf)

    # Create an instance of the application
    note_app = NoteApp()
    # attach an instance of the Author class to the main application
    note_app.author = Author()

    # mount the application on the '/' base path
    cherrypy.tree.mount(note_app, '/', config = application_conf)

    # Start the CherryPy HTTP server
    cherrypy.server.quickstart()
    # Start the CherryPy engine
    cherrypy.engine.start()

下面是 CSS 文件的内容, 请在 note.py 同目录下创建 style.css件, 写入下面的内容.

html, body {
     background-color: #DEDEDE;
     padding: 0px;
     margin: 0px;
     height: 100%;
}

.container {
     border-color: #A1A1A1;
     border-style: solid;
     border-width: 1px;
     background-color: #FFF;
     margin: 10px 150px 10px 150px;
     height: 100%;
     width: 400px;
}

a:link {
     text-decoration: none;
     color: #A1A1A1;
}

a:visited {
     text-decoration: none;
     color: #A1A1A1;
}

a:hover {
     text-decoration: underline;
}

input {
     border: 1px solid #A1A1A1;
}

.form {
     margin: 5px 5px 5px 5px;
}

.info {
     font-size: 70%;
     color: #A1A1A1;
}

在本章接下来的部分我们会引用这个应用来解释 CherryPy 的设计.

5.3. 3.3. 内建 HTTP 服务器

CherryPy 自带了 web (HTTP) 服务器. 这是为了使 CherryPy 完全可以不需要 其他第三方库就可以独立运行(self-contained), 用户可以在很短时间内搭建好 CherryPy 应用的 环境. 正如名称所暗含的, web 服务器是到 CherryPy 应用的出入口, 所有 HTTP 请求和应答都必须通过它. 因此在这个层上处理低级别的 TCP socket, 在 客户端和服务器间传输信息.

CherryPy 并没有强制使用它的内建服务器, 我们可以在需要时使用其他 web 服 务器. 不过在本书中我们只会使用它的内建 web 服务器.

使用下面的调用启动 web 服务器:

cherrypy.server.quickstart()

WARN 在 CP 3.2 中将被废弃, 使用 cherrypy.engine.start() 代替.

5.4. 3.4. 内部引擎

CherryPy 引擎负责管理如下层:

使用下面的调用启动引擎:

cherrypy.engine.start()

5.5. 3.5. 配置

CherryPy 自带了一个配置系统, 你可以使用各种参数控制 HTTP 服务器以及 CherryPy 引擎处理请求 URI 时的行为.

有两种方法配置应用. 首先, 配置可以储存在文本文件中, 使用类似 INI件的语法或是纯 Python 字典格式. 你可以任意选择, 这两种方法可以表达相同 的信息.

CherryPy 提供两种传递配置值的入口 - 使用 cherrypy.config.update() 方法为服务器实例设置全局配置(server)或是使用 cherrypy.tree.mount() 设置特 定应用的配置(per application). 另外, 你还可以为某一路径设置(per path).

配置 CherryPy 服务器实例你需要在设置中指定 global 块.

note 应用中, 我们定义了如下设置:

global_conf = {
        'global': { 'autoreload.on': False,
                        'server.socket_host': '127.0.0.1',
                        'server.socket_port': 8080,
                        'server.protocol_version': 'HTTP/1.1'
                  }}
    application_conf = {
        '/style.css': {
            'tools.staticfile.on': True,
            'tools.staticfile.filename': os.path.join(_curdir, 'style.css'),
            }
        }

这些内容等同于在配置文件中写入:

[global]

server.socket_host = "127.0.0.1"
server.socket_port = 8080

[/style.css]
tools.staticfile.on = True
tools.staticfile.filename = "/full/path/to.style.css"

NOTE 使用文件保存设置的时候, 你必须使用有效 Python 对象
(字符串, 整数, 布尔值等)

我们定义了服务器将要监听传入连接的主机和端口.

然后我们指示 CherryPy 引擎使用 staticfile 工具处理 /style.css 文件, 同时告诉它文件的具体物理路径. 我们将在后面的章节详细解释这些工具, 现在我们只需要认为它可以扩展 CherryPy 的内部特性, 提高处理能力.

为了告知 CherryPy 全局设置, 我们需要执行以下调用:

cherrypy.config.update(conf)

cherrypy.config.update('/path/to/config/file')

我们也可以将设置值传递给已挂载好的应用:

cherrypy.tree.mount(application_instance, script_name, config=conf)

cherrypy.tree.mount(application_instance, script_name, 
                    config='/path/to/config/file')

虽然大多情况下在两者间选择只是喜好的问题, 但有时候某个选择会比另种要更 适合实际应用. 例如, 你需要为配置的某个键传递复杂数据或对象, 那么你会发 现文本文件很难满足你的要求. 另一方面, 如果配置可能会被应用的管理员更改, 那么使用 INI 文件会使任务更容易.

NOTE 如果需要配置应用的某个部分, 例如 Note 应用中的样式表,
你必须调用 cherrypy.tree.mount().

最后一种配置应用的方法是在页面处理器中使用 _cp_config, 或是在包 含页面处理器类中作为类属性使用, 在这种情况下, 该属性影响类中的所有页面 处理器.

在下面的代码例子中, 我们假定 Root 类中除 hello 外的所有其他页面 处理器都会使用gzip 压缩.

import cherrypy

class Root:
      _cp_config = {'tools.gzip.on': True}

      @cherrypy.expose
      def index(self):
          return "welcome"

      @cherrypy.expose
      def default(self, *args, **kwargs):
          return "oops"

      @cherrypy.expose
      # 下一行不起任何作用, 因为我们已经设置了类的 _cp_config 属性.
      # 在一般情况下, 你可以像这样使用修饰器设置 tool.
      # 我们将在后面章节详细讨论.
      @cherrypy.tools.gzip()
      def echo(self, msg):
          return msg

      @cherrypy.expose
      def hello(self):
          return "there"
      hello._cp_config = {'tools.gzip.on': False}

if __name__== '__main__':
      cherrypy.quickstart(Root(), '/')

上面对 quickstart 的调用是下面几条命令的快捷方式:

cherrypy.tree.mount(Root(), '/')
cherrypy.server.quickstart()
cherrypy.engine.start()
# 当然, 第二个可以没有.

你可以使用这个调用在 CherryPy 服务器上挂载单个应用.

最重要的一点是对于每个挂载的应用(不同的前缀), 配置是独立的. 所以上面的 例子中, 应用完全可以挂载在 /myapp 上而不是 /, 使用相同的设置. 设置并不包括前缀, 所以你完全可以认为配置对于应用来说是相对的, 对于挂载 应用的前缀来说是独立的.

NOTE 应用挂载到的前缀指的是 script_name.

5.6. 3.6. 对象发布引擎

HTTP 服务器, 例如 Apache 或 lighttpd, 将请求 URI 映射到文件系统上的路 径, 这样可以使得服务器在处理主要由静态内容(例如图片, 纯 HTML 文件)构成 的站点时很有效.

CherryPy 选择了一个完全不同的方法, 它使用自己的内部查询算法取出请求 URI 所需的页面处理器. CherryPy 2.0 时就决定, 这样的处理器应该是一个 Python 可调用对象, 附加到一个已发布对象的树上. 这就是我们为什么把对象 发布看作请求 URI 到 Python 对象的映射.

CherryPy 定义了两个重要概念:

root = Blog()
root.admin = Admin()
cherrypy.tree.mount(root, '/blog')

在上面的例子中, root 对象被认为是已发布的. 将 admin 对象作为属性扩展到 它上, 那么 admin 对象也是已发布的.

class Root:
     @cherrypy.expose
     def index(self):
         return self.dosome()
     def dosome(self):
         return "hello there"
cherrypy.tree.mount(Root(), '/')

在这个例子中, 到 /dosome 的请求会返回一个 Not Found 错误, 因为该方法没有 被暴露, 即使它属于一个已发布对象. 也就是可调用对象 dosomec 没有作为潜在的 URI 匹配器暴露给内部引擎.

你可以手动设置 exposed 属性或者使用 CherryPyexpose 修饰符暴露一个方 法.

NOTE CherryPy 社区经常将已暴露对象称做 **页面处理器(page handler)**. 本书也将使
用该术语.

例如在 Note 应用中, 已发布对象为 note_appauthor. 树的根部是 note_app, 被挂载在 / 前缀下. 这样, CherryPy 会使用这个对象树处理所有以 / 开头的路径请求. 如果我们使用了诸如 /postit 这样的前缀, 那么 CherryPy会在请求路径符合要求时调用 Note 应用处理请求.

这样你可以将许多应用挂载在不同的前缀下. CherryPy 会根据请求 URI 自动调用. (我们 将在本书后面介绍, 两个应用通过 cherrypy.tree.mount() 挂载, Cherrypy 确保它们 互不影响.)

下面的表格展示了请求 URI 和页面处理器匹配的关系:

请求 URI 路径

已发布对象

页面处理器

/

note_app

index

/author/

note_app.author

index

/author/set

note_app.author

set

/note/1

note_app

note

index()default() 方法是 CherryPy 的特殊页面处理器. 前者匹配以斜杠结 尾的请求 URI, 类似于 Apache 服务器上的 index.html 文件. 后者在 CherryPy 找不 到请求 URI 的明确页面处理器时使用. 我们的 Note 应用没有定义后者, 但它经常用 于捕获异常 URL.

这里我们注意到 /note/1 这个 URI, 事实上, 它匹配的是 note(id);这是由于 CherryPy 支持位置参数. CherryPy 会调用匹配请求 URI 我的第一个页面处理器.

NOTE CherryPy 将 ``/note/1`` 和 ``note?id=1`` 看作是相同的, 只要它能找到一个签名
为 ``note(id)`` 的页面处理器.

下图是 HTTP 请求到达 CherryPy 服务器后的处理过程概览.

!! A image here

5.7. 3.7. 库

CherryPy 提供了很多模块, 涵盖监理 web 应用的常见任务, 例如 session 管理, 静态内 容, 编码处理, 以及基本异常捕获.

5.7.1. 3.7.1. 自动重导入

CherryPy 是一个长时运行的 Python 进程, 所以如果我们修改了应用的一个 Python 模块, 它将不会影响到已存在进程. 手动停止然后重启服务器经常是令人厌烦的工作, 所以 CherryPy 小组加入了自动重导入特性. 一旦 CherryPy 检测到应用导入的某个模块被修改, 它会立即 重启进程. 该特性由配置选项控制.

如果你在产品环境下需要自动重导入特性, 按照下面的配置设置即可. 注意 engine.autoreload_frequency 选项, 它代表自动重导入引擎检查新变化的等待秒数. 默认为一秒.

[global]
server.environment = "production"
engine.autoreload_on = True
engine.autoreload_frequency = 5

自动重导入并不是个模块, 但这里把它作为库提供的一个常见特性.

5.7.2. 3.7.2. cache 模块

在任何 web 应用中缓存都是一个重要的方面, 它能减少不同服务器的压力和负载 - HTTP, 应用, 数据库服务器. 尽管与应用自身有很大关系, 一般的缓存工具, 例如缓存模块所提供 的, 可以使你的应用性能提高不少.

CherryPy 的缓存模块工作在 HTTP 服务器级别, 它将会缓存发送给用户的输出, 并基于预 先定义的关键字(默认为完整 URL)取出已缓存的资源. 缓存被保存在服务器内存里, 所以在 服务器停止的时候缓存会丢失. 你也可以将自己的缓存类传递给 CherryPy , 只需要保证高 等级接口一致即可, 内部处理方式由你自己定义.

5.7.3. 3.7.3. Coverage 模块

创建应用时, 基于应用所处理的输入分析它所执行的步骤是很有利的. 我们可以由此得到潜 在的瓶颈, 检查应用是否按照意愿执行. CherryPy 提供的 coverage 模块对此提供了一个 友好的输出, 显示在运行过程中执行的代码行. 该模块是少数几个需要第三方包支持的模块 之一.

5.7.4. 3.7.4. encoding/decoding 编码/解码模块

在 Web 上发行意味着你需要处理多种字符编码. 你可能只是使用 US-ASCII 发行你的内容, 而忽略读者反馈性息, 或是发布一个可以处理任何字符的公告板应用. CherryPy 提供里一 个编码/解码模块, 它会自动根据服务器或是 user-agent 设置过滤输入和输出.

5.7.5. 3.7.5. HTTP 模块

该模块提供了一些用于处理 HTTP 头, 实体的类和函数.

例如分析 HTTP 请求行和查询字符串:

s = 'GET /note/1 HTTP/1.1' # no query string
r = http.parse_request_line(s) # r 为 ('GET', '/note/1', '', 'HTTP/1.1')
s = 'GET /note?id=1 HTTP/1.1' # 查询字符为 id=1
r = http.parse_request_line(s) # r 为 ('GET', '/note', 'id=1', 'HTTP/1.1')
http.parseQueryString(r[2]) # 返回 {'id': '1'}
提供到 HTTP 头的接口:
例如你有如下的 Accept header 值:
accept_value = "text/xml,application/xml,application/xhtml+xml,text/" \
                "html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
values = http.header_elements('accept', accept_value)
print values[0].value, values[0].qvalue  # 将会打印 text/html 1.0

5.7.6. 3.7.6. Httpauth 模块

该模块提供了基本认证以及 RFC 2617 中所定义摘要认证算法的实现.

5.7.7. 3.7.7. Profiler 模块

该模块用于检查应用的性能.

5.7.8. 3.7.8. Sessions 模块

Web 是基于一个无状态的协议, HTTP, 这意味着请求之间是独立的. 当用户浏览电子商务 网站, 应用需要处理下单等操作. session 机制的出现使得服务器可以记录并跟踪用户的信 息.

CherryPy 的 session 模块提供给应用开发人员一个简单的接口, 可以直接对 session 对 象进行储存, 取出, 修改, 或是删除分枝操作. CherryPy 原生提供了三种不同的 session 对象后端储存:

后端类型

优点

缺点

RAM

高效率 可接受任意对象类型 不需要配置

服务器关闭后数据丢失 内存消耗快速增长

文件系统

信息持久储存 配置简单

文件系统锁效率的 只有可 pickle 对象可以储存

关系数据库(内建 PostgresSQL 支持)

信息持久储存 健壮 可升级 可以做附载均衡

配置较复杂

这样你的应用可以使用一个高层接口而不用去管理底层后端的行为. 这样, 在一开始的 开发中, 你可能使用 RAM session, 而后轻松地切换到 PostgresSQL 后端下, 而不需要对 应用做任何修改. 当然, 你可以使用自己的后端.

5.7.9. 3.7.9. Static 模块

即使是很动态的应用服务器, 也要提供诸如图片和 CSS 这样的静态资源服务. CherryPy供了一个模块, 用来处理静态文件或是整个目录树. 它会自动处理底层 HTTP 交换, 例如 If-Modified-Since 头检查某个资源是在某一日期后是否被修改, 以避免重复不必要的 处理.

5.7.10. 3.7.10. Tidy 模块

即使作为一个 web 开发员, 你也应该保证应用所生成的内容是干净且符合标准的. 如果你 要处理无法完全控制的内容, CherryPy 提供了一个简单的方法, 使用nsgmltidy 这样的工具进行过滤输出内容.

5.7.11. 3.7.11. Wsgiapp 模块

该模块可以将任何 WSGI 应用封装并作为一个 CherryPy 应用使用. 更多关于 WSGI 内容请 参阅第四章.

5.7.12. 3.7.12. XML-RPC 模块

XML-RPC 是一个使用 XML 格式化消息的远程过程调用协议, 通过 HTTP 传输 XML-RPC 客户 端和 XML-RPC 服务器的信息. 一般来说, 客户端创建一个包含需要调用的远程方法名称和 所传递值的 XML 文件, 然后使用 HTTP POST 消息请求服务器. 返回的 HTTP 应答将一个 XML 文档作为字符串发送, 交给客户端处理.

下面的代码例子定义了一个 XML-RPC 服务:

import cherrypy
from cherrypy import _cptools

class Root(_cptools.XMLRPCController):
    @cherrypy.expose
    def echo(self, message):
        return message

if __name__ == '__main__':
    cherrypy.quickstart(Root(), '/xmlrpc')

对应的 XML-RPC 客户端为:

import xmlrpclib
proxy = xmlrpclib.ServerProxy('http://localhost:8080/xmlrpc/')
proxy.echo('hello') # will return 'hello'

5.8. 3.8. 工具

在前一节中, 我们介绍了一些内建模块. CherryPy 为它们提供了一个通用接口, 也就是工具 tool 接口. 你也可以向其中加入你自己的模块.

工具可以从三个不同的上下文环境设置:

conf = {'/': {
               'tools.encode.on': True,
               'tools.encode.encoding': 'ISO-8859-1'
             }
        }
cherrypy.tree.mount(Root(), '/', config=conf)

经常我们需要为匹配某一 URI 的特定对象路径加上额外的处理. 这时使用 Python 函数修 饰符是很方便的. BR

@cherrypy.expose
@cherrypy.tools.encode(encoding='ISO 8859-1')
def index(self)
        return "Et voilà"

工具可以作为普通 Python 可调用对象应用. BR

def index(self):
        cherrypy.tools.accept.callable(media='text/html')

前面的例子展示了如何调用 accept 工具, 它在请求的 Accept HTTP 头中查询提 供的媒体类型( media type ).

所以有了统一接口后, 我们就可以在不影响应用层的情况下修改底层的工具代码.

NOTE 工具就是将第三方组件插入到 CherryPy 引擎, 用于扩展 CherryPy 的一个接口

5.9. 3.9. 错误和异常处理

CherryPy 试着让 web 应用看起来更像是一个富应用. 也就是说和其他 Python 应用一样, 你可能会在页面处理器中引发一个 Python 错误或异常. CherryPy 会捕获它们并根据错误 类型将它们转换为 HTTP 信息.

NOTE 注意当一个异常被引发而且没有被应用的其他部分捕获时, CherryPy 会返回对应的
HTTP 500 错误代码.

下面的例子将展示 CherryPy 的默认行为.

import cherrypy
class Root:
    @cherrypy.expose
    def index(self):
        raise NotImplementedError, "This is an error..."
if __name__ == '__main__':
    cherrypy.quickstart(Root(), '/')

!! A graph here

正如你所见到的 CherryPy 显示出完成的 Python 错误返回信息. 虽然这在开发应用时很有 用, 但在产品模式下这些信息没有用途. 此时, CherryPy 会返回一个默认的错误消息.

!! A graph here

NOTE 在开发模式下, 可以将配置文件中 ``global`` 区块的 ``request.show_tracebacks`` 选项关
闭错误返回信息显示.

CherryPy 在捕获到一个没有被应用开发者处理的错误时会返回一个 HTTP 500 错误代码. HTTP 规定了两类错误代码, 客户端错误 4xx 和服务器错误 5xx. 客户端错误说明用户可能 发送了无效请求(例如没有身份凭证, 请求资源未找到, 等). 服务器错误通知用户在处理请 求时有错误发生, 无法完成请求处理.

应用开发者可以使用 CherryPy 提供的一个简单接口发送正确的错误代码:

cherrypy.HTTPError(error_code, [error_message])

NOTE ``HTTPError`` 会被 CherryPy 引擎捕获, 然后在 HTTP 应答中使用所指定的状态代
码和错误信息.

引发错误时, CherryPy 会将 HTTP 应答正文设置为所提供信息, HTTP 头设置为所定义错误 代码.

import cherrypy
class Root:
    @cherrypy.expose
    def index(self):
        raise cherrypy.HTTPError(401, 'You are not authorized to' \
                                 'access this resource')
if __name__ == '__main__':
    cherrypy.quickstart(Root(), '/')

返回的 HTTP 应答将会是:

HTTP/1.x 401 Unauthorized
Date: Wed, 14 Feb 2007 11:41:55 GMT
Content-Length: 744
Content-Type: text/html
Server: CherryPy/3.0.1alpha

!! A image here

import cherrypy
class Root:
    @cherrypy.expose
    def index(self):
        # shortcut to cherrypy.HTTPError(404)
        raise cherrypy.NotFound
if __name__ == '__main__':
    conf = {'global':{'request.show_tracebacks':False}}
    cherrypy.config.update(conf)
    cherrypy.quickstart(Root(), '/')

!! A image here

那么如何改变 CherryPy 所返回错误页面的布局使之适合你自己的应用呢? 很简单, 通过配 置系统.

import cherrypy
class Root:
    # Uncomment this line to use this template for this level of the
    # tree as well as its sub-levels
    #_cp_config = {'error_page.404': 'notfound.html'}
    @cherrypy.expose
    def index(self):
        raise cherrypy.NotFound
    # Uncomment this line to tell CherryPy to use that html page only
    # for this page handler. The other page handlers will use
    # the default CherryPy layout
    # index._cp_config = {'error_page.404': 'notfound.html'}
if __name__ == '__main__':
    # Globally set the new layout for an HTTP 404 error code
    cherrypy.config.update({'global':{'error_page.404':
                                      'notfound.html' }})
    cherrypy.quickstart(Root(), '/')

notfound.html 文件内容:

<html>
  <head><title>Clearly not around here</title></head>
  <body>
   <p>Well sorry but couldn't find the requested resource.</p>
  </body>
</html>

!! A image here

当捕获到一个 HTTPError 错误时, CherryPy 会在对应页面处理器的配置里查找 error_page.xxx( xxx为所使用的 HTTP 错误代码)记录, 用它替换默认模板.

正如你所看到的, CherryPy 提供了一个高效且易于扩展的错误信息显示机制.

目前为止, 我们以及讨论了 CherryPy 的高层错误处理, 当然, 通过钩子 API 来修改内部 机制也是可行的, 我们将在下章讨论.

5.10. 3.10. 小节

本章介绍了 CherryPy 的一些核心原则, HTTP, 服务器引擎, 以及配置系统. 同时我们简洁 第讨论了对象发布引擎, 它允许 URI 到已暴露 Python 对象的透明映射. 然后回顾了 CherryPy 库中的一些核心模块, 它们大大地增强了 CherryPy 的能力. 最后我们简要介绍 CherryPy 处理错误的方法. 下一章将深入介绍 CherryPy 的内部组件及其特性, 探究我 们以及简单涉及到的话题.

6. 4. 深入了解 CherryPy

第三章介绍了 CherryPy 的一些常见概念, 不过没有过于深入. 在本章, 我们将介绍关键特 性, 例如如何运行多个 HTTP 服务器, 使用额外的 URI 调度器, 使用内建工具及开发新的 工具, 提供静态内容服务, 以及最后 CherryPy 和 WSGI 是如何交互的. 这些使得 CherryPy 成为 web 开发者的一个强大工具. 本章可能会很晦涩, 但是如果你了解了这些, 你将能更自在更有效第使用 CherryPy.

6.1. 4.1. HTTP 协定

CherryPy 一直都致力于发展与 HTTP 标准的一致性 - 首先支持古老的 HTTP/1.0, 然后渐 渐地转换到完全支持 RFC 2616 中定义的 HTTP/1.1. CherryPy 将部分与 HTTP/1.1 一致, 它实现所有的 mustrequired 级别, 但没有实现标准中定义的所有 should 级别. 因此, CherryPy 支持以下 HTTP/1.1 特性:

请求协议

服务器协议

所写应答协议

应答特性集

1.0

1.0

1.0

1.0

1.0

1.1

1.1

1.0

1.1

1.0

1.0

1.0

1.1

1.1

1.1

1.1

NOTE 服务器协议可以通过 ``server.protocol_version`` 键修改.
所写应答协议是 HTTP 应答中返回的版本, 用于通知用户服务器所支持的协议.
应答特性集的协议版本是 CherryPy 在应答处理时内部使用的, 在第二种情况下, CherryPy
将应答限制为 HTTP/1.0 中支持内容.

总而言之, CherryPy 3 提供了大量的 HTTP/1.1 支持, 也正是由于有了这些支持, 它可以 用于许多方案中.

6.2. 4.2. 多个 HTTP 服务器

默认情况下, CherryPy 只启动一个内建 HTTP 服务器实例. 有时候你可能会遇到这些情况:

首先我们看看 CherryPy 的通常启动方式:

conf = {'global': {'server.socket_port': 100100,
                   'server.socket_host': 'localhost'}}
cherrypy.config.update(conf)
cherrypy.server.quickstart()

我们调用服务器对象的 quickstart() 方法, 它会 内建 HTTP 服务器, 并启动它自己 的线程.

假设我们有一个应用, 需要在不同的网络接口上运行; 那么我们应该这样做:

from cherrypy import _cpwsgi
# Create a server on interface 102.168.0.12 port 100100
s1 = _cpwsgi.CPWSGIServer()
s1.bind_addr = ('102.168.0.12', 100100)
# Create a server on interface 102.168.0.27 port 4700
s2 = _cpwsgi.CPWSGIServer()
s2.bind_addr = ('102.168.0.27', 4700)
# Inform CherryPy which servers to start and use
cherrypy.server.httpservers = {s1: ('102.168.0.12', 100100),
                               s2: ('102.168.0.27', 4700)}
cherrypy.server.start()

我们首先创建了两个内建 HTTP 服务器的实例, 并为每个服务器设置 socket 监听请求的绑 定地址.

然后我们将这些服务器附加到 CherryPy 的 HTTP 服务器池中, 调用它的 start()法, 启动这些服务器.

注意我们没有调用 cherrypy.config.update, 因为它会更新所有服务器共享的全局配 置. 不过, 这并不是一个问题, 因为内建服务器的每个实例都有自己的匹配配置键值的属性. 所以:

s1.socket_port = 100100
s1.socket_host = '102.168.0.12'
s1.socket_file = ''
s1.socket_queue_size = 5
s1.socket_timeout = 10
s1.protocol_version = 'HTTP/1.1'
s1.reverse_dns = False
s1.thread_pool = 10
s1.max_request_header_size = 500 * 1024
s1.max_request_body_size = 100 * 1024 * 1024
s1.ssl_certificate = None
s1.ssl_private_key = None

如你所见, 你可以直接设置服务器实例的配置, 而不需要使用全局配置. 这也使得一个应用 同时提供 HTTP 和 HTTPS 服务成为可能, 我们将在第十章对此做详细介绍.

6.3. 4.3. 多线程应用服务器

CherryPy 是以线程模式涉及的. 每次应用在 CherryPy 名称空间里获得 或是设置的一个值(一般是 cherrypy.requestcherrypy.response对象), 都是 在多线程环境下发生的. cherrypy.requestcherrypy.response 都是线程数据 容器, 也就是说你的通过确认哪个请求是通过它们代理来独立调用它们.

使用内建 CherryPy 服务器时, 一个用于处理到达请求的线程池会被创建. 线程池的打消通 server.thread_pool 键来确定, 默认为 10. 虽然听起来创建大一点的线程池来提 高服务器性能是个好注意, 但事实上并不是这样.

该值必须作为每应用需求来配置. 事实上如果你的应用评价请求处理时间很短的话, 那么很 可能线程在很长一段时间内都处于空闲状态. 创建大的线程池会导致其中大部分线程只是占 用内容, 而不会提高任何性能. 所以我们建议最好在不同配置下运行性能测试, 决定最适合 的线程数量.

应用服务器使用线程模式并不总是被看好, 因为线程的使用会导致诸如同步需求等问题. 也 有一些其他方法可以达到目的, 例如:

总之, 对于哪个解决方法更好的讨论会是无休止的, 这样的问题也从来不会得到真正回答. 事实上, 每种环境都需要一种不同的解决方法.

6.4. 4.4. URI 调度处理

在第三章我们介绍国, CherryPy 默认将 URI 映射到 exposed 属性为 TruePython 可调用对象上. CherryPy 社区需要一个更具有弹性的调度器解决方法. 所以 CherryPy 3 提供了三种另外的内建调度器, 并提供了一个自定义调度器的简单方法.

6.4.1. 4.4.1. HTTP 方法调度器

在一些应用中, URI 与服务器所执行的动作之间是独立的. 例如下面的 URI:

http://somehost.com/album/delete/12

如上, URI 包含客户端想要执行的操作. 默认的 CherryPy 调度器会将它映射到:

album.delete(12)

这很不错, 但是你可能希望吧操作从 URI 自身移出, 就像:

http://somehost.com/album/12

你要奇怪了, 那服务器如何能知道执行什么操作呢. 这是通过 HTTP 请求自身完成的, 多亏 我们有 HTTP 方法:

DELETE /album/12 HTTP/1.1

页面处理器会按照下面的方法来处理这样的请求:

class Album:
     exposed = True
     def GET(self, id):
          ....
     def POST(self, title, description):
          ....
     def PUT(self, id, title, description):
          ....
     def DELETE(self, id):
          ....

使用 HTTP 方法调度器时, 将调用页面处理器 album.DELETE(12).

从前边的类定义中你可能发现, 方法并没有设置 exposed 属性, 而类本身具有该属性. 这样做的原因与这个调度器的实现方法有关.

当请求到达服务器时, CherryPy 查找最匹配的页面处理器. 如果找到的页面处理器使用的 是 HTTP 方法调度器, 那么页面处理器事实上是 URI 目标资源的概念化表示, 在我们的例 子中是 Album 类. 调度器会检查类中是否有匹配请求 HTTP 方法的类方法. 如果有的 话, 调度器将使用剩下的参数调用它. 否则它将立即向客户端发回一个 HTTP 405 错误 (Method Not Allowed), 通知客户端它无法使用指定 HTTP 方法, 所以不能执行对特定 资源的操作.

例如若我们没有在 Album 类中定义 DELETE, 那么就会得到这样的错误代码.

在所有情况下, CherryPy 会自动在应答中加入 Allowe HTTP 头, 来告知客户端它能对 资源所使用的方法.

NOTE 注意在这种情况下 CherryPy 不会查找 ``index`` 或 ``default`` 的
URI-to-object 页面处理器. 这是由于基于 URI 调度和基于 URI+HTTP 方法调度之间的基
本原则不同. 第六章将继续深入这个内容.

为了启用 HTTP 方法调度器, 你必须将目标路径的 request.dispatch 键设置为一个该 调度器的实例.

例如如果整个应用都使用该技术的话, 我们应该使用:

{'/' : {'request.dispatch': cherrypy.dispatch.MethodDispatcher()}}

HTTP 方法调度器经常用在符合 REST 原则的应用中, 我们将在第六章对此进行讨论.

6.4.2. 4.4.2. Routes 调度器

在 URI-to-object 和 HTTP 方法调度器中, 我们都没有明确地声明 URI 与页面处理器之间 的联合关系; 而是将查询对应关系的工作留给了 CherryPy 引擎. 许多开发者更喜欢明确地 指出对应关系, 决定 URI 如何映射到页面处理器.

所以, 在使用 Routes 调度器时, 你比如将匹配 URI 的模式连接到一个指定页面处理器上.

我们来看一个例子:

import cherrypy
class Root:
    def index(self):
        return "Not much to say"
    def hello(self, name):
        return "Hello %s" % name
if __name__ == '__main__':
    root = Root()
    # Create an instance of the dispatcher
    d = cherrypy.dispatch.RoutesDispatcher()
    # connect a route that will be handled by the 'index' handler
    d.connect('default_route', '', controller=root)
    # connect a route to the 'hello' handler
    # this will match URIs such as '/say/hello/there'
    # but not '/hello/there'
    d.connect('some_other', 'say/:action/:name',
              controller=root, action='hello')
    # set the dispatcher
    conf = {'/': {'request.dispatch': d}}
    cherrypy.quickstart(root, '/', config=conf)

NOTE 当使用 Routes 调度器时, 你不需要设置 ``exposed`` 属性.

Routes 调度器的 connect 方法定义如下:

connect(name, route, controller, **kwargs)

connect 方法的参数说明:

关于该包是如何工作的请参阅官方 Routes 文档.

CherryPy 的 Routes 调度器在把 URI 匹配到任何 route 的时候默认并不会传递它映射器 返回的 actioncontroller 值. 它们在 CherryPy 应用中不是很重要. 不过如 果你需要它们的话你可以在 Routes 调度器构造函数里设置 fetch_result 参数为 True. 这样两个值就会传递到页面处理器, 此时千万要记得给页面处理器加上 controlleraction 参数.

6.4.3. 4.4.3. 虚拟主机调度器

你可能需要在一个 CherryPy 服务器上将不同的 web 应用部署到给定的不同域名. CherryPy 提供了一个简单的方法, 如下面的例子:

import cherrypy
class Site:
    def index(self):
        return "Hello, world"
    index.exposed = True

class Forum:
    def __init__(self, name):
        self.name = name
    def index(self):
        return "Welcome on the %s forum" % self.name
    index.exposed = True

if __name__ == '__main__':
    site = Site()
    site.cars = Forum('Cars')
    site.music = Forum('My Music')
    hostmap = {'www.ilovecars.com': '/cars',
               'www.mymusic.com': '/music',}
    cherrypy.config.update({'server.socket_port': 80})
    conf = {'/': {'request.dispatch':
                            cherrypy.dispatch.VirtualHost(**hostmap)}}
    cherrypy.tree.mount(site, config=conf)
    cherrypy.server.quickstart()
    cherrypy.engine.start()

首先


CherryPyEssentials (last edited 2009-12-25 07:09:17 by localhost)