延伸?

Cherrypy是一個真正開放的框架,您可以隨意擴展和插入服務器端或按請求的新功能。不管怎樣,Cherrypy都可以幫助您構建應用程序,并通過簡單的模式支持您的體系結構。

服務器范圍的功能?

Cherrypy既可以被視為HTTP庫,也可以被視為Web應用程序框架。在后一種情況下,它的體系結構提供了支持跨整個服務器實例操作的機制。這提供了一個強大的畫布來執行持久的操作,因為服務器范圍的函數位于請求處理本身之外。只要公共汽車還活著,它們就可以在整個過程中使用。

典型用例:

  • 保留與外部服務器的連接池,這樣您就不必在每次請求時(例如數據庫連接)重新打開它們。

  • 后臺處理(假設您需要在不阻塞整個請求本身的情況下完成工作)。

發布/訂閱模式?

Cherrypy的主干包括一個總線系統,它實現了一個簡單的 publish/subscribe messaging pattern .簡單地說,在奇瑞,一切都是通過那輛公共汽車來控制的。人們可以很容易地把巴士想象成壽司餐廳的腰帶,如下圖所示。

_images/sushibelt.JPG

您可以訂閱和發布到總線上的頻道。通道有點像總線中的唯一標識符。當消息發布到一個信道時,總線將把消息發送給該信道的所有訂戶。

pubsub模式的一個有趣的方面是它促進了調用者和被調用者之間的分離。已發布的消息最終將生成響應,但發布者不知道該響應來自何處。

由于這種分離,Cherrypy應用程序可以輕松地訪問功能,而無需保存對提供該功能的實體的引用。相反,應用程序只是發布到總線上,并接收適當的響應,這才是最重要的。

典型模式?

讓我們采用以下虛擬應用程序:

import cherrypy

class ECommerce(object):
    def __init__(self, db):
        self.mydb = db

    @cherrypy.expose
    def save_kart(self, cart_data):
        cart = Cart(cart_data)
        self.mydb.save(cart)

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

應用程序引用了數據庫,但這在數據庫提供程序和應用程序之間創建了相當強的耦合。

另一種解決耦合問題的方法是使用pubsub工作流:

import cherrypy

class ECommerce(object):
    @cherrypy.expose
    def save_kart(self, cart_data):
        cart = Cart(cart_data)
        cherrypy.engine.publish('db-save', cart)

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

在這個例子中,我們發布了 cart 實例到 db-save 頻道。然后,一個或多個訂戶可以對該消息作出反應,應用程序不必知道這些消息。

注解

這種方法不是強制性的,而是由您決定如何設計實體交互。

實施細節?

Cherrypy的總線實現是簡單的,因為它向通道注冊函數。每當消息發布到某個通道時,每個已注冊的函數都將應用作為參數傳遞的消息。

整個行為是同步發生的,從這個意義上說,如果訂閱服務器處理消息花費的時間太長,那么剩余的訂閱服務器將被延遲。

Cherrypy的總線不是一個高級的pubsub消息傳遞代理系統,如 zeromqRabbitMQ .使用它的前提是它可能有成本。

作為Pubsub總線的引擎?

如前所述,Cherrypy是圍繞一輛公共巴士建造的??蚣茉谶\行時管理的所有實體都在單個總線實例上工作,該實例名為 engine .

因此,總線實現提供了一組描述應用程序生命周期的公共通道:

                 O
                 |
                 V
STOPPING --> STOPPED --> EXITING -> X
   A   A         |
   |    \___     |
   |        \    |
   |         V   V
 STARTED <-- STARTING

狀態的轉換觸發要發布到的通道,以便訂戶能夠對其作出反應。

一個很好的例子是HTTP服務器,它將從 "STOPPED" 向A陳述 "STARTED" 每當消息發布到 start 通道。

內置通道?

為了支持其生命周期,Cherrypy定義了一組公共渠道,這些渠道將在不同的州發布:

  • “開始” :當總線在 "STARTING" 狀態

  • “主” :定期從Cherrypy的主循環

  • “停止” :當總線在 "STOPPING" 狀態

  • “優雅” :總線請求重新加載訂閱服務器時

  • “退出” :當總線在 "EXITING" 狀態

此頻道將由發布到 engine 自動。因此,注冊任何需要對 engine .

此外,在請求處理期間,還向發布了一些其他通道。

  • "before_request" :在Cherrypy處理請求之前

  • "after_request" :處理后立即

另外,從 cherrypy.process.plugins.ThreadManager 插件:

  • "acquire_thread"

  • "start_thread"

  • "stop_thread"

  • "release_thread"

總線API?

為了使用總線,實現提供了以下簡單的API:

  • 這個 channel 參數是一個字符串,用于標識消息應發送到的通道

  • *args 是消息,可以包含任何有效的python值或對象。

  • 這個 channel 參數是一個字符串,用于標識 callable 將注冊到。

  • callable 是簽名必須與將要發布的內容匹配的python函數或方法。

  • 這個 channel 參數是一個字符串,用于標識 callable 已注冊到。

  • callable 是注冊的python函數或方法。

插件?

簡單地說,插件是通過發布或訂閱頻道來使用總線的實體,通常兩者同時使用。

重要

插件在您具有以下功能時非常有用:

  • 整個應用服務器都可用

  • 與應用程序的生命周期關聯

  • 您希望避免與應用程序強耦合

創建插件?

一個典型的插件如下所示:

import cherrypy
from cherrypy.process import wspbus, plugins

class DatabasePlugin(plugins.SimplePlugin):
    def __init__(self, bus, db_klass):
        plugins.SimplePlugin.__init__(self, bus)
        self.db = db_klass()

    def start(self):
        self.bus.log('Starting up DB access')
        self.bus.subscribe("db-save", self.save_it)

    def stop(self):
        self.bus.log('Stopping down DB access')
        self.bus.unsubscribe("db-save", self.save_it)

    def save_it(self, entity):
        self.db.save(entity)

這個 cherrypy.process.plugins.SimplePlugin 是Cherrypy提供的幫助程序類,它將自動訂閱 startstop 相關渠道的方法。

startstop 頻道在上發布,這些方法將相應地調用。

注意我們的插件如何訂閱 db-save 這樣總線就可以向插件發送消息。

啟用插件?

要啟用該插件,必須將其注冊到總線,如下所示:

DatabasePlugin(cherrypy.engine, SQLiteDB).subscribe()

這個 SQLiteDB 這里有一個假類,用作我們的數據庫提供程序。

禁用插件?

還可以按如下方式注銷插件:

someplugin.unsubscribe()

當您希望阻止Cherrypy啟動默認HTTP服務器時,通常會使用此選項,例如,如果您在不同的HTTP服務器(支持wsgi)上運行:

cherrypy.server.unsubscribe()

讓我們看一個使用這個默認應用程序的例子:

import cherrypy

class Root(object):
    @cherrypy.expose
    def index(self):
        return "hello world"

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

例如,運行此應用程序時,您將看到:

[27/Apr/2014:13:04:07] ENGINE Listening for SIGHUP.
[27/Apr/2014:13:04:07] ENGINE Listening for SIGTERM.
[27/Apr/2014:13:04:07] ENGINE Listening for SIGUSR1.
[27/Apr/2014:13:04:07] ENGINE Bus STARTING
[27/Apr/2014:13:04:07] ENGINE Started monitor thread 'Autoreloader'.
[27/Apr/2014:13:04:08] ENGINE Serving on http://127.0.0.1:8080
[27/Apr/2014:13:04:08] ENGINE Bus STARTED

現在讓我們取消訂閱HTTP服務器:

import cherrypy

class Root(object):
    @cherrypy.expose
    def index(self):
        return "hello world"

if __name__ == '__main__':
    cherrypy.server.unsubscribe()
    cherrypy.quickstart(Root())

這就是我們得到的:

[27/Apr/2014:13:08:06] ENGINE Listening for SIGHUP.
[27/Apr/2014:13:08:06] ENGINE Listening for SIGTERM.
[27/Apr/2014:13:08:06] ENGINE Listening for SIGUSR1.
[27/Apr/2014:13:08:06] ENGINE Bus STARTING
[27/Apr/2014:13:08:06] ENGINE Started monitor thread 'Autoreloader'.
[27/Apr/2014:13:08:06] ENGINE Bus STARTED

如您所見,服務器沒有啟動。失蹤者:

[27/Apr/2014:13:04:08] ENGINE Serving on http://127.0.0.1:8080

按請求函數?

Web應用程序開發中最常見的任務之一是根據運行時上下文調整請求的處理。

在Cherrypy中,這是通過所謂的 工具 .如果您熟悉django或wsgi中間件,那么cherrypy工具在精神上是相似的。它們添加在請求/響應處理期間應用的函數。

鉤點?

掛接點是請求/響應處理過程中的一個點。

下面是您可以掛起工具的“鉤子點”的簡要說明:

  • "on_start_resource" -最早的鉤子;請求行和請求頭已經被處理,調度器已經設置了request.handler和request.config。

  • "before_request_body" -這里連接的工具在處理請求主體之前運行。

  • "before_handler" -就在request.handler之前 exposed 調用調度器找到的可調用項)。

  • "before_finalize" -這個鉤子在頁面處理程序被處理之后和Cherrypy格式化最終響應對象之前被調用。例如,它可以幫助您檢查頁面處理程序可能返回的內容,并根據需要更改一些標題。

  • "on_end_resource" -處理完成-已準備好返回響應。這并不總是意味著request.handler(公開的頁處理程序)已經執行!可能是發電機。如果您的工具絕對需要在頁面處理程序生成響應主體之后運行,那么您需要改為在請求時使用,或者將response.body包裝在生成響應主體時應用工具的生成器中。

  • "before_error_response" -在設置錯誤響應(狀態代碼、正文)之前立即調用。

  • "after_error_response" -在設置錯誤響應(狀態代碼、正文)之后和錯誤響應完成之前立即調用。

  • "on_end_request" -請求/響應對話結束,所有數據都已寫入客戶端,此處不再顯示任何內容,請繼續。

工具?

工具是一個簡單的可調用對象(函數、方法、實現 __call__ 方法)附加到 hook point .

下面是一個附加到 before_finalize 掛接點,因此在調用頁處理程序之后:

@cherrypy.tools.register('before_finalize')
def logit():
   print(cherrypy.request.remote.ip)

還可以手動創建和分配工具。裝飾師注冊相當于:

cherrypy.tools.logit = cherrypy.Tool('before_finalize', logit)

使用該工具簡單如下:

class Root(object):
    @cherrypy.expose
    @cherrypy.tools.logit()
    def index(self):
        return "hello world"

顯然,該工具可以聲明為 other usual ways .

注解

工具的名稱,從技術上講,屬性設置為 cherrypy.tools ,不必與可調用文件的名稱匹配。但是,在配置中將使用該名稱來引用該工具。

有狀態工具?

工具機制非常靈活,支持豐富的按請求功能。

如前一節所示的直工具通常足夠好。但是,如果您的工作流在請求處理期間需要某種狀態,您可能需要基于類的方法:

import time

import cherrypy

class TimingTool(cherrypy.Tool):
    def __init__(self):
        cherrypy.Tool.__init__(self, 'before_handler',
                               self.start_timer,
                               priority=95)

    def _setup(self):
        cherrypy.Tool._setup(self)
        cherrypy.request.hooks.attach('before_finalize',
                                      self.end_timer,
                                      priority=5)

    def start_timer(self):
        cherrypy.request._time = time.time()

    def end_timer(self):
        duration = time.time() - cherrypy.request._time
        cherrypy.log("Page handler took %.4f" % duration)

cherrypy.tools.timeit = TimingTool()

此工具計算頁面處理程序為給定請求所花費的時間。它存儲處理程序將要被調用的時間,并在處理程序返回其結果后立即記錄時間差。

導入位是 cherrypy.Tool 構造函數允許您注冊到一個鉤子點,但要將同一個工具附加到另一個鉤子點,必須使用 cherrypy.request.hooks.attach 方法。這個 cherrypy.Tool._setup 當工具應用于請求時,CherryPy會自動調用方法。

接下來,讓我們看看如何使用我們的工具:

class Root(object):
    @cherrypy.expose
    @cherrypy.tools.timeit()
    def index(self):
        return "hello world"

工具排序?

因為您可以在同一個鉤子點注冊許多工具,所以您可能會想知道它們將按什么順序應用。

Cherrypy提供了一種確定性的,但又很簡單的機制。只需設置 優先 屬性值從1到100,值越小優先級越高。

如果為多個工具設置相同的優先級,則將按照在配置中聲明它們的順序調用它們。

工具盒?

所有內置的cherrypy工具都被收集到一個名為 cherrypy.tools .它響應中的配置項 "tools" namespace .您可以將自己的工具添加到此工具箱中,如上所述。

如果需要更模塊化,您還可以自己制作工具箱。例如,您可以創建多個用于使用JSON的工具,或者發布一組涉及身份驗證和授權的工具,每個人都可以從中受益(提示、提示)。創建一個新工具箱非常簡單:

import cherrypy

# Create a new Toolbox.
newauthtools = cherrypy._cptools.Toolbox("newauth")

# Add a Tool to our new Toolbox.
@newauthtools.register('before_request_body')
def check_access(default=False):
    if not getattr(cherrypy.request, "userid", default):
        raise cherrypy.HTTPError(401)

然后,在應用程序中,像使用一樣使用它 cherrypy.tools 在應用程序中注冊工具箱的附加步驟。請注意,這樣做會自動注冊 "newauth" config namespace;您可以在下面的操作中看到config條目:

import cherrypy

class Root(object):
    @cherrypy.expose
    def default(self):
        return "Hello"

conf = {
   '/demo': {
       'newauth.check_access.on': True,
       'newauth.check_access.default': True,
    }
}

app = cherrypy.tree.mount(Root(), config=conf)

請求參數操作?

HTTP使用字符串在兩個端點之間傳輸數據。但是,您的應用程序可以更好地使用更豐富的對象類型。由于讓每個頁面處理程序對數據進行反序列化既不是真正可讀的,也不是維護方面的好主意,因此將此函數委托給工具是一種常見的模式。

例如,假設查詢字符串中有一個用戶ID,數據庫中存儲了一些用戶數據。您可以檢索數據,創建一個對象并將其傳遞給頁面處理程序,而不是用戶ID。

import cherrypy

class UserManager(cherrypy.Tool):
    def __init__(self):
        cherrypy.Tool.__init__(self, 'before_handler',
                               self.load, priority=10)

    def load(self):
        req = cherrypy.request

        # let's assume we have a db session
        # attached to the request somehow
        db = req.db

        # retrieve the user id and remove it
        # from the request parameters
        user_id = req.params.pop('user_id')
        req.params['user'] = db.get(int(user_id))

cherrypy.tools.user = UserManager()


class Root(object):
    @cherrypy.expose
    @cherrypy.tools.user()
    def index(self, user):
        return "hello %s" % user.name

換言之,Cherrypy賦予您以下能力:

  • 將不屬于初始請求的數據注入到頁面處理程序中

  • 同時刪除數據

  • 將數據轉換為另一個更有用的對象,以從頁處理程序本身中消除這種負擔。

定制調度員?

分派是為給定請求定位適當的頁面處理程序的藝術。通常,調度基于請求的URL、查詢字符串,有時還基于請求的方法(get、post等)。

基于此,Cherrypy已經配備了各種調度員。

然而,在某些情況下,您需要更多。下面是一個調度器示例,它將始終確保傳入的URL指向小寫的頁面處理程序。

import random
import string

import cherrypy
from cherrypy._cpdispatch import Dispatcher

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

class ForceLowerDispatcher(Dispatcher):
    def __call__(self, path_info):
        return Dispatcher.__call__(self, path_info.lower())

if __name__ == '__main__':
    conf = {
        '/': {
            'request.dispatch': ForceLowerDispatcher(),
        }
    }
    cherrypy.quickstart(StringGenerator(), '/', conf)

運行此代碼段后,請轉到:

在這兩種情況下,您將被引導到 generate 頁面處理程序。如果沒有我們自制的調度員,第二個調度員會失敗并返回404錯誤 (RFC 7231#section-6.5.4

工具還是調度員??

在前面的例子中,為什么不簡單地使用一個工具呢?好吧,找到頁面處理程序后,調用工具的時間越早。在我們的示例中,已經太遲了,因為默認調度器甚至找不到匹配的 /GENerAte .

調度器的存在主要是為了確定為請求的資源提供服務的最佳頁面處理程序。

另一方面,有一些工具可以使請求的處理適應應用程序的運行時上下文和請求的內容。

通常,只有當您有一個非常具體的用例來定位最合適的頁面處理程序時,才需要編寫一個調度器。否則,默認的就足夠了。

請求正文處理器?

自3.2版本發布以來,Cherrypy提供了一種非常優雅和強大的機制,以基于其mimetype處理請求的主體。請參閱 cherrypy._cpreqbody 模塊了解如何實現自己的處理器。