先進的?

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中運行測試)是禁用默認啟用的交互模式??梢越迷O置 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 .