先進的?
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:
http://hostname/<artist>/
http://hostname/<artist>/albums/<album_title>/
很明顯,您不會創建一個以世界上每個可能的波段命名的頁面處理程序。這意味著您將需要一個頁面處理程序作為所有頁面處理程序的代理。
默認調度器無法單獨處理該方案,因為它希望在源代碼中顯式聲明頁處理程序。幸運的是,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服務器和代碼之間的對話:

請注意,HTTP服務器首先收集所有輸出,然后立即將所有內容寫入客戶機:狀態、頭和正文。對于靜態或簡單的頁面來說,這很好,因為整個響應可以在任何時候更改,無論是在應用程序代碼中,還是在Cherrypy框架中。
“流輸出”如何與Cherrypy一起工作?
當您將配置條目“response.stream”設置為true(并使用“yield”)時,cherrypy將管理HTTP服務器和代碼之間的對話,如下所示:

流式處理時,應用程序不會立即將原始正文內容傳遞回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的回答包括一個屬性:
response.time
:time.time()
反應開始時
處理信號?
這個 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頁面更安全。其中包括:
傳輸數據:
使用安全cookie
呈現頁面:
設置httponly cookies
設置xframe選項
啟用XSS保護
設置內容安全策略
實現這一點的一個簡單方法是使用工具設置頭文件并用它包裝整個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 333 和 PEP 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 .或者,也可以使用 ORM 如 SQLAlchemy 或 SQLObject .
你可以在 cherrypy-recipes 這解釋了如何使用 plugins 和 tools .
HTML模板支持?
測試應用程序?
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
環境變量到 False
或 0
.
如果您不想更改環境變量來簡單地運行一組測試,您也可以將 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 .