十多年来,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. JSTL标签语言

    ******概述: JSTL是apache对EL表达式的扩展(也就是说JSTL依赖EL),JSTL是标签语言! JSTL标签使用以来非常方便,它与JSP动作标签一定,只不过它不是JSP内置的标签,需要 ...

  2. 20175215 2018-2019-2 第二周java课程学习总结

    一.学生免费申请使用IDEA 下载好IDEA后,设置到最后有一个界面, 我们需要到IDEA官网进行IDEA免费试用权的申请,如果有学校的邮箱,使用学校的邮箱注册并证明是自己的就可以直接通过申请.如下图 ...

  3. pom文件中的dependencyManagement和dependencies的区别

    dependencies 子项目中,自动继承父项目中的相关依赖 dependencyManagement 只是声明依赖,并不实现引入,因此子项目中需要显示的声明需要用的依赖.如果不在子项目中声明依赖, ...

  4. WIN10下命令行禁用编辑模式

    在开发的时候 控制台输出信息 点一下右键就进入编辑模式了,WIN7没有这个问题.网上搜了一下 说是要 禁用编辑模式,下面是代码VS2005可用 { #ifndef ENABLE_EXTENDED_FL ...

  5. linux查询端口被哪个程序使用了

    使用如下命令查询8000端口被哪个程序使用 netstat -tunlp|

  6. leetcode-hard-array-54. Spiral Matrix-NO

    mycode 思路:这种方格图一定要预先设置定位的变量,例如最大的长.宽,变化中的长.宽,在while循环中也要不断判断是否满足break条件 class Solution(object): def ...

  7. form 组件如何扩展user的字段

    第一步 先将一个包引进来 第二步创建一个类加入你想要的字段

  8. VIM速查表-转

    在linux上一直使用vim,慢慢熟悉了它的命令,才终于领悟了什么是编辑器之神. 最近抽空整理了这份速查表,收获颇丰,并分享给大家. 进入vim vim配置 移动光标 屏幕滚动 插入文本类 删除命令 ...

  9. OPCDAAuto的一个坑

    最近项目需要对SCADA系统的下位机采集实时数据,常见做法是两种,一种采用ModBus RTU/TCP协议直接通过支持ModBus的下位机通信,一种是通过OPC规范,使用厂商提供的OPC Server ...

  10. python3.6+RF连接mysql

    接口自动化中会遇到有操作数据库的动作 目录 1.安装第三方库 2.安装pymysql 3.数据库操作 1.安装第三方库 使用在线安装:pip install robotframework_databa ...