十多年来,Web 程序设计人员一直使用 CGI 将应用程序连接到 Web 服务器和另一端的 Web 浏览器。有很多理由建议使用 CGI:它可以与任何编程语言一起使用,并且它在 Web 服务器和宿主服务上得到极其广泛的支持。遗憾的是,CGI 也有着严重的不足。Web 服务器和 CGI 脚本之间的接口令人费解, 此外,Web 服 务器对每个 CGI 请求产生单独的进程,这就意味着性能差、在所有的请求之间没有持续性。

这些年来,不满的黑客已经创造了多种替代方式来在 Web 服务器和应用程序代码之间架起桥梁。在最近几年里,完成这一工作的流行方式包括 Java™ servlets、Ruby on Rails 框架,以及 mod_perl 和 mod_python 这两个 Apache 模块。

这些桥梁数量非常巨大,以致从中挑选一个都很困难,这一问题在 Python 领域尤其突出。某些服务器应用程序桥梁是非常完善的应用程序框架,拥有它们自己的模板系统、身份验证服务、与对象相关的映射程序,以及其他诸如此类的功能。由于如此多的选择提供了如此多需要学习的功能,所以,没有空闲时间的程序设计人员一直还在使用他们已经学会的东西,这并不足以为怪。

本文将介绍 CherryPy,一种用于 Python 的、简单而又非常有用的 Web 框架。它所做的全部工作就是以尽可能少的忙乱将 Web 服务器与您的 Python 代码连接。它并不决定使用哪些其他工具,所以您可以从自己的角度自由地选择模板系统、数据库映射程序或者其他工具。我将说明如何使用 CherryPy 编写应用程序。本文假定您已经拥有了一些关于 Python 以及 HTTP 请求和响应如何工作的知识。

CherryPy 请求

CherryPy 不是依靠 Apache 或者另外的 Web 服务器,而是运行它自己的小型的基于 Python 的 Web 服务器。传统的 Web 服务器在目录树磁盘之外创建一个 Web 空间,而 CherryPy 服务器则在 Python 对象树之外创建它的 Web 空间。

考虑一下对于 URL http://localhost:8080/hello/ 的请求。在传统的 Web 服务器中,这种 URL 相应于 Web 空间根部下面的 hello/ 目录。当您使用 Web 浏览器访问它时,Web 服务器读取 hello/ 目录中的 index.html 文件或者调用该目录中的 index.cgi 脚本作为一个 CGI,并且向您发送输出。

CherryPy Web 服务器不提供根植于磁盘目录中的 Web 空间,而提供根植于特定的 Python 对象 cpg.root 中的 Web 空间。若要访问那个对象的每个方法和成员,只需将它的名称附加到根 URL 上即可。所以,hello/ URL 在 CherryPy 中相应于 cpg.root 的成员 hello

通过定义一个方法为请求提供服务

如果 cpg.root.hello 是一个方法,则 CherryPy 调用该方法,方法的输出被发送到 Web 浏览器。下面的代码定义一个暴露 hello 方法的对象:

清单 1. 定义一个暴露 hello 方法的对象
#!/usr/bin/env python
from cherrypy import cpg
class Application:
@cpg.expose
def hello(self):
return "Hello, world!" cpg.root = Application()
cpg.server.start()

通过 Python 运行这段脚本,CherryPy Web 服务器将会启动。只要该脚本一直在运行,您就可以访问 http://localhost:8080/hello/,并且可以看到字符串 Hello, world!。(当然,这还得假定您没有在端口 8080 上运行一台服务器。)如果您访问其他 URL,包括 Web 服务器根,将会得到一个 CherryPy 错误,因为 /hello/ 是该应用程序知道如何提供服务的惟一 URL。

通过定义对象树为请求提供服务

如果 cpg.root.hello 是一个对象,而不是方法,那么当用户访问 /hello/ 时,CherryPy 会调用 hello 对象的 index() 方法。这些代码以与先前示例相同的方式提供 /hello/ URL 服务:

清单 2. CherryPy 调用 hello 对象的 index() 方法
#!/usr/bin/env python
from cherrypy import cpg
class HelloWorld:
@cpg.expose
def index(self):
return "Hello, world!"
class Application:
hello = HelloWorld() cpg.root = Application()
cpg.server.start()

当您访问 /hello/ URL 时,您的请求被映射到对象 cpg.root.hello,并且其默认的方法(index())被调用来处理该请求。

 

回页首

暴露对象和方法

方法 hello 和 index 上的 装饰符 @cpg.expose 有什么用呢?它告诉 CherryPy,调用该方法来响应 Web 请求没有问题。

在提供静态文件的 Web 网站,假定 Web 空间中的每个文件和目录都计划用于公共消耗。但是也有几处例外,例如,Apache 就不会提供隐藏的文件,如 UNIX® 系统上的 .htaccess。

当您将一个对象树暴露为一个 CherryPy URL 空间时,假设是另一种情形:除非您显性地将方法标记为暴露,否则它不会为外面的用户提供服务。考虑一下许多编程语言中公有和私有类成员之间的区别,(并且通过 Python 中的 _method() 约定非正式地实施)。一个设计良好的 CherryPy 类可能只有少数几个公有的方法暴露给 Web 客户机,但是它很可能有许多不允许客户机直接访问的内部方法。

我认为装饰符是将方法暴露给 CherryPy 的最佳方式,但是也可以通过将其成员 exposed 设置为 True 来暴露一个方法:

清单 3. 通过将其成员 exposed 设置为 True 来暴露一个方法
class HelloWorld:
def index(self):
return "Hello, world!"
index.exposed = True

装饰符不存在于版本 2.4 之前的 Python 中,因而 exposed 这个技巧可能是您将方法暴露给 CherryPy 的惟一方式。

 

回页首

收集用户输入

当用户向 CGI 脚本提交表单或者提供信息时,Web 浏览器会收集那些信息,并且通过环境变量将其传递给该脚本。脚本的责任是解析和理解这些信息,尽管有一套约定控制着这些信息的格式和用途。CherryPy 使用这些非正式的约定消除这个过程的几个步骤,以参数的形式将用户输入提供给它决定调用的 Python 方法。

将查询字符串转变为关键字参数

考虑一个具有查询参数的 URL,比如 http://localhost:8080/hello/?what=hello&who=world。用户可能已经点击这个 URL,或者已将它提交为填充 HTML 表单的结果。传统的基于 CGI 的 Web 服务器将把 what=hello&who=world 传递到环境变量 QUERY_STRING 中的 CGI 脚本。CGI 脚本负责取出该变量,解析那个字符串。Python 的 cgi 模块为您进行解析。但是,使用 CherryPy,您不必做任何事情。CherryPy Web 服务器自动将 URL 的查询字符串转变为一组关键字参数。

清单 4. CherryPy Web 服务器自动将 URL 的查询字符串转变为一组关键字参数
class Application:
@cpg.expose
def hello(self, what='Hello', who='world'):
return '%s, %s!' % (what, who)

当用户点击 /hello/ URL 时,CherryPy 将查询字符串中的 what 和 who 转换为 hello() 方法的参数。从 URL 到 Python 方法调用的转换对您来说完全是透明的。它甚至不在乎原始的 HTTP 请求是通过方法 GET 进来的,还是通过方法 POST 进来的。

将额外的路径部分转换为位置参数

CGI 脚本用户输入的其他来源是环境变量 PATH_INFO。它通常用于使 Web 应用程序的 URL 看起来更像真实的 Web 页。例如,考虑 URL http://localhost:8080/hello/world/。如果 /hello/ 指定了一个 CGI 脚本,那么访问 /hello/world/ 会将 PATH_INFO 环境变量设置为 /world/ 而调用那个脚本。

在 CGI 环境中,是由 CGI 脚本负责解析额外路径信息。但是利用查询字符串参数,CherryPy 基于典型的使用约定为您进行解析。查询字符串键值对成为 CherryPy 应用程序中的关键字参数,而额外路径信息参数则成为对象的 default() 方法的位置参数:

清单 5. 额外路径信息参数成为对象的 default() 方法的位置参数
class Hello:
@cpg.expose
def default(self, who):
return 'Hello, %s!' % who
class Application:
hello = Hello()
cpg.root = Application()
cpg.server.start()

/hello/world/ URL 的 hello/ 部分被映射为 cpg.root.hello,这是 Hello 的一个实例。对象 Hello 没有叫做 world 的方法或者成员对象,所以这个 URL 的 world 部分作为那个对象的 default() 方法的位置参数被传递进来。

 

回页首

读取自请求对象的标题

CherryPy 解析用户请求的 URL,并使用适当的参数传送到 Python 方法,但是一个 HTTP 请求不只是一个 URL。那进来的 HTTP 标题又如何呢?

CherryPy 方法都具有对叫做 cgp.request 的对象的访问权,该对象包含有许多关于用户的 HTTP 请求的信息。这个对象最有意思的成员是requestMap,它包含与 Web 请求相关的所有进入的 HTTP 标题:

清单 6. requestMap 包含与 Web 请求相关的进入的 HTTP 标题
class Application:
@cpg.expose
def index(self):
items = [x + ': ' + y for x,y in cpg.request.headerMap.items()]
return "<br />".join(items)

运行该应用程序并访问 http://localhost:8080/,您将会看到您的浏览器与其请求一起发送的所有 HTTP 标题的列表清单。

 

回页首

写到响应对象

对 HTTP 请求怎样,对响应也就怎样。CherryPy 应用程序方法通常情况下会以字符串的形式返回响应的正文,但是有时需要您设置附加的 HTTP 标题,进行一个重定向,或者改变 HTTP 响应代码。您可以借助对每个方法都可用的对象 cpg.response 来完成所有这些事情。

cpg.response.headerMap 是外出 HTTP 标题的映射,就像 cpg.request.requestMap 是进入标题的映射一样:

清单 7. cpg.response.headerMap 是外出 HTTP 标题的映射
#!/usr/bin/env python
from cherrypy import cpg
class Application:
@cpg.expose
def setHeader(self, header, value):
"""Hit the '/setHeader?header=Value&foo=bar' URL to get a
response in which the HTTP header "foo" has a value of
"bar"."""
cpg.response.headerMap[header] = value
return 'Set HTTP response header "%s" to "%s"' % (header, value)

HTTP 状态代码只不过是一个叫做 Status 的 HTTP 标题,所以您可以将它设置为 404、503,或者您需要的其他状态:

清单 8. 设置 HTTP 标题状态
    @cpg.expose
def forbidden(self):
"Hit the '/forbidden' URL to be denied access."
cpg.response.headerMap['Status'] = '503 Forbidden'
return "You don't have permission to access this resource."

若要将某个 HTTP 重定向,您可以手工设置 Status 和 Location 标题,或者可以使用 CherryPy 的 httputils 助手库的 redirect 方法,它完成相同的工作:

清单 9. 使用 CherryPy 的 httputils 助手库的 redirect 方法
    @cpg.expose
def redirect(self):
"Hit the '/redirect' URL to be redirected."
from cherrypy.lib import httptools
httptools.redirect('./destination')
@cpg.expose
def destination(self):
"This is where you end up if you hit the '/redirect' URL."
from cherrypy.lib import httptools
cpg.response.headerMap['Content-Type'] = 'text/plain'
return 'Here is some plain text.'
cpg.root = Application()
cpg.server.start()
 

回页首

在一个会话中保持持续信息

考虑一个这样的应用程序,在其中我可以点击 URL /name/set?name=leonardr 来设置一些数据,然后我可以点击 URL /name/show,并被告知Your previously set name is leonardr。由于第二个请求使用来自第一个请求的信息,所以这两个请求都必须是单个会话的组成部分。我第一次请求中发送的字符串 leonardr 存储在服务器的某个位置。当我进行第二次请求时,不知何故我被认为是发送第一个请求的同一人,并检索到我与第一个请求一起发送的信息。

CherryPy 隐藏了这种复杂性的大部分,使得易于建立和使用基于 cookie 的会话 —— 这是一个重要的功能,但是并不是 CGI 本身就支持的功能。您可以将任何 Python 对象存储在 cpg.request.sessionMap 映射中,下次相同的用户点击您的页面时它就在那儿。下面的示例与刚才描述的应用程序的工作原理相似:

清单 10. 将任何 Python 对象存储在 cpg.request.sessionMap 映射中
#!/usr/bin/env python
from cherrypy import cpg
class Application:
@cpg.expose
def set(self, name):
cpg.request.sessionMap['name'] = name
return 'Set name to %s' % name
@cpg.expose
def show(self):
return 'Your previously set name is %s.' % \
cpg.request.sessionMap.get('name', '[none]')

到目前为止都相当简单。可是,为了这些代码起作用,需要设置 CherryPy 服务器将会话 cookie 与每个 HTTP 响应相关联。否则,CherryPy 就永不可能将两个请求与相同的会话关联起来。您可以在 CherryPy 服务器配置文件中进行这些配置(参见 参考资料),但是比较易于证明它是启动 CherryPy Web 服务器的 Python 代码的一部分:

清单 11. 启动 CherryPy Web 服务器的 Python 代码的一部分
cpg.root = Application()
cpg.server.start(configMap={'sessionStorageType' : 'ram',
'sessionCookieName' : 'CherryPySessionCookie',
'sessionTimeout' : 60}) #Session expires in an hour
 

回页首

结束语

CherryPy 使用与 CGI 相同的概念将 Web 服务器与 Web 应用程序绑定起来,但是通过在单个进程内处理它的所有请求,改善了性能,并且获得了请求间的持续性。因为它只是绑定到 Python 代码,所以它并不需要经常隐藏 CGI 的信息传递技术,这样导致更易理解的更少代码行。CherryPy 是 CGI 的优秀替代品,并且是创建 Python Web 应用程序的良好基础。

cherrypy的更多相关文章

  1. cherrypy应用探究

    1. cherrypy是什么? cheerypy是一个有pythonic特性的面向对象的http服务框架. 玩python的人都应该知道pythonic这个单词.python大神给我们的建议 : &g ...

  2. Example: Develop Web application on Baidu App Engine using CherryPy

    In the past few months, I have developed two simple applications on Baidu App Engine. Compared to Go ...

  3. python - ImportError: No module named http.cookies error when installing cherrypy 3.2 - Stack Overflow

    python - ImportError: No module named http.cookies error when installing cherrypy 3.2 - Stack Overfl ...

  4. Cherrypy文件上传非ASCII文件名乱码问题解决

    Cherrypy 版本: 18.0.1 由于某些特殊原因(可能是与标准兼容的问题),Cherrypy对上传文件的原文件名使用 ISO-8859-1 编码方式解码,导致非 ASCII 的文件名显示为乱码 ...

  5. AttributeError: 'Request' object has no attribute 'json', cherrypy 无法接收到json字符串,解决方法

    @cherrypy.expose @cherrypy.tools.accept(media="application/json")   #加入这个装饰器 @cherrypy.too ...

  6. bottle+cherrypy快速开发web服务

    我目前用得最顺手的python web框架是bottle,简单方便. bottle有一个开发用的http服务器,效率不高,单线程,阻塞. 所以,得找个别的服务器来部署. 根据bottle官方的文档,发 ...

  7. MethodDispatcher—Cherrypy对REST的支持

    前言 CherryPy是Python的一个Web框架,通过MethodDispatcher内建了对REST的支持,而且使用非常方便. 示例 首先,我们需要有一个符合REST风格的资源(Resource ...

  8. cherrypy入门

    主要是一个简单的cherrypy hello例子 import cherrypy from jinja2 import Environment, FileSystemLoader import os ...

  9. cherrypy & gevent patch

    给cherrypy 打gevent WSGIServer的patch 1. patch Serving 类 2. 关闭python的原生WSGIServer 具体使用例子参考 我的开源项目  http ...

  10. CherryPy 入门

    CherryPy是一个Python的HTTP框架,可以用Python来处理HTTP请求然后返回结果. 1. 安装 可以去这个地址下载 CherryPy-3.1.2.win32.exe .或者去这个链接 ...

随机推荐

  1. python3笔记十一:python数据类型-List列表

    一:学习内容 列表概念 列表创建:创建空列表.创建带有元素的列表 列表访问:取值 列表修改:替换元素.追加元素.追加列表.插入元素 列表删除:移除列表中指定下标处的元素.移除匹配条件的第一个元素.移除 ...

  2. 构建基于Electron开发的软件遇到的问题

    构建pdman时,报了好些错. 主要还是网络问题和版本不一致导致的. 前提 npm设置淘宝源,自行搜索. 版本 上面是官方要求的node环境. 需要首先安装nvm, brew install nvm ...

  3. What is the !! (not not) operator in JavaScript?

    What is the !! (not not) operator in JavaScript? 解答1 Coerces强制 oObject to boolean. If it was falsey ...

  4. java.sql.SQLSyntaxErrorException: ORA-01795: 列表中的最大表达式数为 1000

    后台报了一些异常日志,查阅后发现在 oracle 数据库中使用 in 关键字条件不能超过 1000 个,当时写查询语句时没有关注这个问题 总结一下解决方法 1.分多次查询,对查询要求不高的话.把入参的 ...

  5. Android WebView使用与JavaScript使用

    WebView基本使用 WebView是View的一个子类,可以让你在activity中显示网页. 可以在布局文件中写入WebView:比如下面这个写了一个填满整个屏幕的WebView: <?x ...

  6. leetcode 121买卖股票的最佳时机I

    从下标1开始,维护两个变量,一个是0~i-1中的最低价格low,一个是当前的最高利润res;先更新最高利润,在更新最低价格:应用了贪心算法的基本思想,总是选择买入价格最低的股票,代码如下: 具有最优子 ...

  7. inner join, left join, right join, full outer join的区别

    总的来说,四种join的区别可以描述为: left join 会从左表(shop)那里返回所有的记录,即使在右表(sale_detail)中没有匹配的行. right outer join 右连接,返 ...

  8. win10安装mysql-最简单教程

    工具下载地址 链接: https://pan.baidu.com/s/1XL2wUDrcd9NpT8NOcXoDTQ 提取码: vu34 下载好之后解压. 在目录D:\Program Files\my ...

  9. MySQL 树形结构 根据指定节点 获取其所有叶子节点

    背景说明 需求:MySQL树形结构, 根据指定的节点,获取其下属的所有叶子节点. 叶子节点:如果一个节点下不再有子节点,则为叶子节点. 问题分析 1.可以使用类似Java这种面向对象的语言,对节点集合 ...

  10. RTX修改标题logo方法

    摘要: 打开“腾讯通RTX管理器”→“配置向导”→“服务运行状态”→“停止所有服务”,退出“腾讯通RTX管理器”按照如下操作.①修改到期时间为:9999-12-300:0:0 用记事本打开“C:/Pr ...