先进的

Cherrypy支持这些部分将描述的更高级的功能。

设置页面处理程序的别名

cherrypy.expose() decorator支持别名。

让我们使用提供的模板 tutorial 03

import random
import string

import cherrypy

class StringGenerator(object):
    @cherrypy.expose(['generer', 'generar'])
    def generate(self, length=8):
        return ''.join(random.sample(string.hexdigits, int(length)))

if __name__ == '__main__':
    cherrypy.quickstart(StringGenerator())

在本例中,我们为页面处理程序创建本地化别名。这意味着可以通过以下方式访问页面处理程序:

  • /生成

  • /Generer(法语)

  • /Generar(西班牙语)

显然,您的别名可能是任何适合您需要的。

注解

别名可以是单个字符串或它们的列表。

休息式调度

术语 RESTful URL 有时用于讨论友好的URL,这些URL可以很好地映射到应用程序公开的实体。

重要

我们将不讨论什么是RESTful,什么不是RESTful,但我们将展示两种机制来在您的Cherrypy应用程序中实现通常的想法。

假设您希望创建一个应用程序来公开音乐乐队及其唱片。您的应用程序可能具有以下URL:

很明显,您不会创建一个以世界上每个可能的波段命名的页面处理程序。这意味着您将需要一个页面处理程序作为所有页面处理程序的代理。

默认调度器无法单独处理该方案,因为它希望在源代码中显式声明页处理程序。幸运的是,Cherrypy提供了支持这些用例的方法。

参见

本节引自 stackoverflow response .

特殊的调度方法

_cp_dispatch 是您在任何 controller 在Cherrypy开始处理之前按摩剩余的部分。这为您提供了删除、添加或以其他方式处理您希望的任何段的能力,甚至可以完全更改其余部分。

import cherrypy

class Band(object):
    def __init__(self):
        self.albums = Album()

    def _cp_dispatch(self, vpath):
        if len(vpath) == 1:
            cherrypy.request.params['name'] = vpath.pop()
            return self

        if len(vpath) == 3:
            cherrypy.request.params['artist'] = vpath.pop(0)  # /band name/
            vpath.pop(0) # /albums/
            cherrypy.request.params['title'] = vpath.pop(0) # /album title/
            return self.albums

        return vpath

    @cherrypy.expose
    def index(self, name):
        return 'About %s...' % name

class Album(object):
    @cherrypy.expose
    def index(self, artist, title):
        return 'About %s by %s...' % (title, artist)

if __name__ == '__main__':
    cherrypy.quickstart(Band())

注意控制器如何定义 _cp_dispatch ,它只需要一个参数,URL路径信息被分割成若干段。

该方法可以检查和操作段列表,可以在任何位置删除任何段或添加新段。然后将新的段列表发送给调度器,调度器将使用它来定位适当的资源。

在上面的示例中,您应该能够转到以下URL:

这个 /nirvana/ 段与带和 /nevermind/ 片段与专辑相关。

为了实现这一点, _cp_dispatch 方法的工作原理是,默认调度器根据页面处理程序签名及其在处理程序树中的位置匹配URL。

在本例中,我们获取URL中的动态段(带和记录名称),将它们注入请求参数中,然后将它们从段列表中删除,就好像它们从未出现过一样。

换言之, _cp_dispatch 使其看起来像在处理以下URL:

Popargs装饰师

cherrypy.popargs() 更直截了当的是,它给了Cherrypy无法解释的任何部分一个名字。这使得片段与页面处理程序签名的匹配更容易,并帮助Cherrypy了解URL的结构。

import cherrypy

@cherrypy.popargs('band_name')
class Band(object):
    def __init__(self):
        self.albums = Album()

    @cherrypy.expose
    def index(self, band_name):
        return 'About %s...' % band_name

@cherrypy.popargs('album_title')
class Album(object):
    @cherrypy.expose
    def index(self, band_name, album_title):
        return 'About %s by %s...' % (album_title, band_name)

if __name__ == '__main__':
    cherrypy.quickstart(Band())

这与 _cp_dispatch 但是,如上所述,它更为明确和本地化。上面写着:

  • 取第一段并将其存储到名为 band_name

  • 再次获取第一个段(因为我们删除了前一个段),并将其存储到名为 album_title

注意,修饰符接受不止一个绑定。例如:

@cherrypy.popargs('album_title')
class Album(object):
    def __init__(self):
        self.tracks = Track()

@cherrypy.popargs('track_num', 'track_title')
class Track(object):
    @cherrypy.expose
    def index(self, band_name, album_title, track_num, track_title):
        ...

这将处理以下URL:

最后请注意,如何将整个段堆栈传递给每个页面处理程序,以便获得完整的上下文。

错误处理

CherryPy的 HTTPError 类支持在出现错误时立即发出响应。

class Root:
    @cherrypy.expose
    def thing(self, path):
        if not authorized():
            raise cherrypy.HTTPError(401, 'Unauthorized')
        try:
            file = open(path)
        except FileNotFoundError:
            raise cherrypy.HTTPError(404)

HTTPError.handle 是一个上下文管理器,它支持将应用程序中引发的异常转换为适当的HTTP响应,如第二个示例所示。

class Root:
    @cherrypy.expose
    def thing(self, path):
        with cherrypy.HTTPError.handle(FileNotFoundError, 404):
            file = open(path)

流式处理响应主体

Cherrypy处理HTTP请求,打包和解包低级细节,然后将控制权传递给应用程序 page handler 从而产生响应体。Cherrypy允许您返回各种类型的正文内容:字符串、字符串列表和文件。Cherrypy还允许你 产量 内容,而不是 返回 内容。当您使用“yield”时,您还可以选择流式传输输出。

一般来说,不流输出更安全、更容易。 因此,流输出在默认情况下是关闭的。流输出和使用会话需要很好地理解 how session locks work .

“正常”切瑞皮反应过程

当您从页面处理程序提供内容时,Cherrypy会像这样管理HTTP服务器和代码之间的对话:

_images/cpreturn.gif

请注意,HTTP服务器首先收集所有输出,然后立即将所有内容写入客户机:状态、头和正文。对于静态或简单的页面来说,这很好,因为整个响应可以在任何时候更改,无论是在应用程序代码中,还是在Cherrypy框架中。

“流输出”如何与Cherrypy一起工作

当您将配置条目“response.stream”设置为true(并使用“yield”)时,cherrypy将管理HTTP服务器和代码之间的对话,如下所示:

_images/cpyield.gif

流式处理时,应用程序不会立即将原始正文内容传递回Cherrypy或HTTP服务器。相反,它传递回一个发电机。在这一点上,Cherrypy最终确定了状态和标题, 之前 发电机已被消耗,或已产生任何输出。这对于允许HTTP服务器在消息头和消息体可用时发送消息头和消息体片段是必要的。

一旦Cherrypy设置了状态和头,它就会将它们发送到HTTP服务器,然后由HTTP服务器将它们写出给客户机。从那时起,cherrypy框架基本上已经过时了,HTTP服务器基本上直接从应用程序代码(页面处理程序方法)请求内容。

因此,当进行流式处理时,如果页面处理程序中发生错误,Cherrypy将无法捕获它——HTTP服务器将捕获它。因为头(可能还有一些主体)已经写入客户机,所以服务器 不能 了解处理错误的安全方法,因此只需关闭连接(当前的内置服务器实际上会在正文中写出一条简短的错误消息,但这可能会发生更改,并且不能保证您可能与CherryPy一起使用的所有HTTP服务器的行为)。

此外,如果页处理程序方法是流生成器,则不能手动修改页处理程序中的状态或头,因为在头写入客户端之前,不会对该方法进行迭代。 这包括引发异常,如httperror、notfound、internalredirect和httpredirect。 要在修改标题时使用流生成器,您必须返回一个独立于(或嵌入)页面处理程序的生成器。例如:

class Root:
    @cherrypy.expose
    def thing(self):
        cherrypy.response.headers['Content-Type'] = 'text/plain'
        if not authorized():
            raise cherrypy.NotFound()
        def content():
            yield "Hello, "
            yield "world"
        return content()
    thing._cp_config = {'response.stream': True}

流生成器很性感,但它们会破坏HTTP。Cherrypy允许您为特定情况流式输出:需要花费数分钟才能生成的页面,或者需要立即将部分内容输出到客户机的页面。由于上述问题, 通常最好扁平化(缓冲)内容,而不是流内容 .否则,只有当流媒体的好处大于风险时才能这样做。

响应时间

Cherrypy的回答包括一个属性:

处理信号

这个 engine plugin 自动实例化为 cherrypy.engine.signal_handler .然而,它只是 已订阅 自动由 cherrypy.quickstart() .因此,如果您想要信号处理,并且正在呼叫:

tree.mount()
engine.start()
engine.block()

在启动发动机之前,请务必自行添加:

engine.signals.subscribe()

Windows控制台事件

Microsoft Windows使用控制台事件来传递某些信号,如ctrl-c。在Windows平台上部署cherrypy需要 Python for Windows Extensions ,它是自动安装的,与环境标记有额外的依赖关系。安装后,Cherrypy将自动处理ctrl-c和其他控制台事件(ctrl-c-u事件、ctrl-logoff-u事件、ctrl-break-u事件、ctrl-shutdown-u事件和ctrl-close-u事件),关闭总线以准备退出进程。

保护服务器安全

注解

本节并不是保护Web应用程序或生态系统的完整指南。请查看提供的各种指南 OWASP .

有几种设置可以使Cherrypy页面更安全。其中包括:

传输数据:

  1. 使用安全cookie

呈现页面:

  1. 设置httponly cookies

  2. 设置xframe选项

  3. 启用XSS保护

  4. 设置内容安全策略

实现这一点的一个简单方法是使用工具设置头文件并用它包装整个Cherrypy应用程序:

import cherrypy

# set the priority according to your needs if you are hooking something
# else on the 'before_finalize' hook point.
@cherrypy.tools.register('before_finalize', priority=60)
def secureheaders():
    headers = cherrypy.response.headers
    headers['X-Frame-Options'] = 'DENY'
    headers['X-XSS-Protection'] = '1; mode=block'
    headers['Content-Security-Policy'] = "default-src 'self';"

注解

阅读更多有关 those headers .

然后,在 configuration file (或您要启用该工具的任何其他位置):

[/]
tools.secureheaders.on = True

如果你使用 sessions 您还可以启用这些设置:

[/]
tools.sessions.on = True
# increase security on sessions
tools.sessions.secure = True
tools.sessions.httponly = True

如果使用SSL,还可以启用严格的传输安全性:

 # add this to secureheaders():
 # only add Strict-Transport headers if we're actually using SSL; see the ietf spec
 # "An HSTS Host MUST NOT include the STS header field in HTTP responses
 # conveyed over non-secure transport"
 # http://tools.ietf.org/html/draft-ietf-websec-strict-transport-sec-14#section-7.2
 if (cherrypy.server.ssl_certificate != None and cherrypy.server.ssl_private_key != None):
headers['Strict-Transport-Security'] = 'max-age=31536000'  # one year

接下来,您可能应该使用 SSL .

多个HTTP服务器支持

每当您启动引擎时,Cherrypy都会启动自己的HTTP服务器。在某些情况下,您可能希望在多个端口上承载应用程序。这很容易实现:

from cherrypy._cpserver import Server
server = Server()
server.socket_port = 8090
server.subscribe()

您可以创建尽可能多的 server 服务器实例,根据需要,一次 subscribed 他们将遵循奇瑞比发动机的生命周期。

WSGi支持

Cherrypy支持在中定义的wsgi接口 PEP 333 以及在 PEP 3333 .其含义如下:

  • 您可以用Cherrypy服务器托管外部WSGi应用程序

  • Cherrypy应用程序可以由另一个WSGi服务器托管。

使您的Cherrypy应用程序成为WSGi应用程序

可以从您的应用程序中获取wsgi应用程序,如下所示:

import cherrypy
wsgiapp = cherrypy.Application(StringGenerator(), '/', config=myconf)

只需使用 wsgiapp 任何WSGi感知服务器中的实例。

在Cherrypy中宿主国外的WSGi应用程序

假设您有一个支持wsgi的应用程序,您可以使用 cherrypy.tree.graft 设施。

def raw_wsgi_app(environ, start_response):
    status = '200 OK'
    response_headers = [('Content-type','text/plain')]
    start_response(status, response_headers)
    return ['Hello world!']

cherrypy.tree.graft(raw_wsgi_app, '/')

重要

不能将工具与外部WSGi应用程序一起使用。但是,您仍然可以从 CherryPy bus .

不需要wsgi接口?

默认的cherrypy HTTP服务器支持在中定义的wsgi接口。 PEP 333PEP 3333 .但是,如果您的应用程序是纯Cherrypy应用程序,那么您可以切换到一个完全通过wsgi层的HTTP服务器。它将提供轻微的性能提升。

import cherrypy

class Root(object):
    @cherrypy.expose
    def index(self):
        return "Hello World!"

if __name__ == '__main__':
    from cherrypy._cpnative_server import CPHTTPServer
    cherrypy.server.httpserver = CPHTTPServer(cherrypy.server)

    cherrypy.quickstart(Root(), '/')

重要

使用本机服务器,您将无法移植wsgi应用程序,如前一节所示。这样做将导致运行时出现服务器错误。

WebSocket支持

WebSocket 是HTML5工作组为响应双向通信需求而开发的最新应用程序协议。已经提出了各种各样的黑客攻击,如Comet、轮询等。

WebSocket是一个从HTTP升级请求开始其生命周期的套接字。执行升级后,底层套接字将保持打开状态,但不再用于HTTP上下文。相反,两个连接的端点都可以使用套接字将数据推送到另一端。

Cherrypy本身不支持WebSocket,但该功能由名为 ws4py .

数据库支持

Cherrypy不捆绑任何数据库访问,但其体系结构使集成通用数据库接口(如中指定的db-api)变得容易。 PEP 249 .或者,也可以使用 ORMSQLAlchemySQLObject .

你可以在 cherrypy-recipes 这解释了如何使用 pluginstools .

HTML模板支持

Cherrypy不提供任何HTML模板,但它的体系结构使其易于集成。流行的是 MakoJinja2 .

你会发现 here 关于如何使用混合集成它们的方法 pluginstools .

测试应用程序

Web应用程序和任何其他类型的代码一样,必须进行测试。Cherrypy提供了 helper class 以便于编写功能测试。

下面是一个基本Echo应用程序的简单示例:

import cherrypy
from cherrypy.test import helper

class SimpleCPTest(helper.CPWebCase):
    def setup_server():
        class Root(object):
            @cherrypy.expose
            def echo(self, message):
                return message

        cherrypy.tree.mount(Root())
    setup_server = staticmethod(setup_server)

    def test_message_should_be_returned_as_is(self):
        self.getPage("/echo?message=Hello%20world")
        self.assertStatus('200 OK')
        self.assertHeader('Content-Type', 'text/html;charset=utf-8')
        self.assertBody('Hello world')

    def test_non_utf8_message_will_fail(self):
        """
        CherryPy defaults to decode the query-string
        using UTF-8, trying to send a query-string with
        a different encoding will raise a 404 since
        it considers it's a different URL.
        """
        self.getPage("/echo?message=A+bient%F4t",
                     headers=[
                         ('Accept-Charset', 'ISO-8859-1,utf-8'),
                         ('Content-Type', 'text/html;charset=ISO-8859-1')
                     ]
        )
        self.assertStatus('404 Not Found')

如您所见,测试继承自该助手类。您应该设置您的应用程序并按常规安装它。然后,定义各种测试并调用助手 getPage() 执行请求的方法。只需使用各种专门的assert*方法来验证您的工作流和数据。

然后可以使用 py.test 如下:

$ py.test -s test_echo_app.py

这个 -s 是必需的,因为CherryPy类也包装stdin和stdout。如果没有该标志,测试可能会挂起等待输入的失败断言。

避免这个问题的另一个选择(例如,如果您正在IDE中运行测试)是禁用默认启用的交互模式。可以禁用设置 WEBTEST_INTERACTIVE 环境变量到 False0 .

如果您不想更改环境变量来简单地运行一组测试,您也可以将 helper class ,集合 helper.CPWebCase.interactive = False 然后从自定义类派生所有测试类:

import cherrypy
from cherrypy.test import helper

class TestsBase(helper.CPWebCase):

    helper.CPWebCase.interactive = False

注解

尽管它们是使用典型的模式编写的, unittest 模块支持,它们不是裸单元测试。实际上,一个完整的cherrypy堆栈是为您启动并运行您的应用程序的。如果您真的想对Cherrypy应用程序进行单元测试,也就是说不必启动服务器,您可能想看看这个 recipe .

注解

这个 helper class 来源于 unittest.TestCase 班级。因为这个原因,从 pytest ,在标准方面有一些限制 pytest 测试,尤其是在测试类中对测试进行分组时。更多详情请访问 this page .