部署?

Cherrypy獨立存在,但作為一個應用服務器,它通常位于共享或復雜的環境中。因此,在反向代理之后運行cherrypy或使用其他服務器托管應用程序并不少見。

注解

多年來,Cherrypy的服務器已經足夠可靠和快速。如果你接收到的流量是平均的,那么它自己就能做得很好。盡管如此,將靜態內容的服務委托給更強大的服務器(如 nginx 或cdn。

作為守護進程運行?

Cherrypy允許您使用傳統的雙叉輕松地將當前流程與父環境分離:

from cherrypy.process.plugins import Daemonizer
d = Daemonizer(cherrypy.engine)
d.subscribe()

注解

這個 engine plugin 僅在提供 fork() .

如果復刻子進程中發生啟動錯誤,則父進程返回的代碼仍然為0。初始守護進程中的錯誤仍然返回正確的退出代碼,但fork之后的錯誤不會返回。因此,如果使用此插件進行守護,則不要將返回代碼用作進程是否完全啟動的準確指示器。實際上,該返回代碼僅指示進程是否成功完成了第一個復刻。

該插件采用可選參數重定向標準流: stdin , stdoutstderr .默認情況下,這些都重定向到 /dev/null ,但您可以將它們發送到日志文件或其他地方。

警告

在這個插件運行之前,您應該小心不要啟動任何線程。如果您這樣做,插件將發出警告,因為“…調用函數(在對fork()的調用和對exec函數的調用之間需要某些資源)的效果未定義”。 (ref )。因此,服務器插件以優先級75運行(它啟動工作線程),這比守護進程的默認優先級65晚。

以其他用戶身份運行?

使用這個 engine plugin 以根用戶身份啟動Cherrypy站點(例如,在80這樣的特權端口上監聽),然后將特權減少到更受限制的地方。

此插件的“開始”偵聽器的優先級略高于 server.start 為了方便最常見的使用:從一個低端口(需要根)開始,然后放到另一個用戶。

DropPrivileges(cherrypy.engine, uid=1000, gid=1000).subscribe()

PID文件?

pidfile文件 engine plugin 非常簡單:它在啟動時將進程ID寫入一個文件,在退出時刪除該文件。必須提供“pidfile”參數,最好是絕對路徑:

PIDFile(cherrypy.engine, '/var/run/myapp.pid').subscribe()

系統D插座激活?

socket activation是一個systemd特性,它允許設置一個系統,以便systemd可以坐在一個端口上“按需”啟動服務(有點像inetd和xinetd)。

Cherrypy具有內置的套接字激活支持,如果從SystemD服務文件運行,它將檢測 LISTEN_PID 環境變量要知道,它應該將fd 3視為傳遞的套接字。

要了解有關套接字激活的詳細信息,請訪問:http://0pointer.de/blog/projects/socket-activation.html

通過主管控制?

Supervisord 是一個強大的過程控制和管理工具,可以圍繞過程監控執行許多任務。

下面是Cherrypy應用程序的一個簡單的主管配置。

[unix_http_server]
file=/tmp/supervisor.sock

[supervisord]
logfile=/tmp/supervisord.log ; (main log file;default $CWD/supervisord.log)
logfile_maxbytes=50MB        ; (max main logfile bytes b4 rotation;default 50MB)
logfile_backups=10           ; (num of main logfile rotation backups;default 10)
loglevel=info                ; (log level;default info; others: debug,warn,trace)
pidfile=/tmp/supervisord.pid ; (supervisord pidfile;default supervisord.pid)
nodaemon=false               ; (start in foreground if true;default false)
minfds=1024                  ; (min. avail startup file descriptors;default 1024)
minprocs=200                 ; (min. avail process descriptors;default 200)

[rpcinterface:supervisor]
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface

[supervisorctl]
serverurl=unix:///tmp/supervisor.sock

[program:myapp]
command=python server.py
environment=PYTHONPATH=.
directory=.

這可以通過 server.py 模塊作為應用程序入口點。

import cherrypy

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


cherrypy.config.update({'server.socket_port': 8090,
                        'engine.autoreload.on': False,
                        'log.access_file': './access.log',
                        'log.error_file': './error.log'})
cherrypy.quickstart(Root())

接受配置(假設它保存在一個名為 supervisor.conf )計入:

$ supervisord -c supervisord.conf
$ supervisorctl update

現在,您可以將瀏覽器指向http://localhost:8090/,它將顯示 Hello World! .

要停止主管,請鍵入:

$ supervisorctl shutdown

這顯然會關閉應用程序。

SSL支持?

注解

您可能希望使用來自的服務測試服務器的SSL。 Qualys, Inc.

Cherrypy可以使用SSL加密連接以創建HTTPS連接。這樣可以保證網絡流量的安全。方法如下。

  1. 生成私鑰。我們將使用openssl并遵循 OpenSSL Keys HOWTO

$ openssl genrsa -out privkey.pem 2048

您可以創建需要密碼才能使用的密鑰,也可以創建不帶密碼的密鑰。用密碼保護您的私鑰要安全得多,但要求您每次使用密鑰時都輸入密碼。例如,在啟動或重新啟動Cherrypy服務器時,可能需要輸入密碼。這可能可行,也可能不可行,具體取決于您的設置。

如果需要密碼,請添加 -aes128 , -aes192-aes256 切換到上面的命令。您不應該使用任何DES、3DES或SEED算法來保護您的密碼,因為它們是不安全的。

SSL實驗室建議使用2048位RSA密鑰來實現安全性(請參閱末尾的參考部分)。

  1. 生成證書。我們將使用openssl并遵循 OpenSSL Certificates HOWTO .讓我們從自我簽名的測試證書開始:

$ openssl req -new -x509 -days 365 -key privkey.pem -out cert.pem

然后,OpenSSL將向您提出一系列問題。您可以輸入任何適用的值,或將大多數字段留空。唯一的領域是你 must 填充是“公用名”:輸入用于訪問網站的主機名。如果您只是在自己的計算機上創建要測試的證書,并且通過在瀏覽器中鍵入“localhost”來訪問服務器,請輸入公用名“localhost”。

  1. 決定是使用python的內置ssl庫,還是使用pyopenssl庫。Cherrypy也支持。

    1. Built-in. 要使用python的內置ssl,請在cherrypy配置中添加以下行:

    cherrypy.server.ssl_module = 'builtin'
    
    1. pyopenssl公司 .因為在第一次創建cherrypy時,python沒有內置的ssl庫,所以默認設置是使用pyopenssl。要使用它,您需要安裝它(我們可以建議您安裝 cython 第一個):

    $ pip install cython, pyOpenSSL
    
  2. 在cherrypy配置中添加以下行以指向證書文件:

cherrypy.server.ssl_certificate = "cert.pem"
cherrypy.server.ssl_private_key = "privkey.pem"
  1. 如果手頭有證書鏈,還可以指定它:

cherrypy.server.ssl_certificate_chain = "certchain.perm"
  1. 正常啟動Cherrypy服務器。請注意,如果在本地調試和/或使用自簽名證書,瀏覽器可能會向您顯示安全警告。

WSGi服務器?

嵌入到另一個WSGi框架中?

雖然Cherrypy附帶了一個非??煽亢妥銐蚩斓腍TTP服務器,但是您可能希望將您的Cherrypy應用程序集成到不同的框架中。為此,我們將從中定義的wsgi接口中獲益。 PEP 333PEP 3333 .

請注意,在第三方wsgi服務器中嵌入cherrypy時,應該遵循一些基本規則:

  • 如果你依靠 "main" 要發布的頻道,就像在Cherrypy的主循環中一樣,您應該找到一種方法在另一個框架的主循環中發布到該頻道。

  • 啟動Cherrypy的引擎。這將發布到 "start" 總線的通道。

    cherrypy.engine.start()
    
  • 當你終止時,停止Cherrypy的引擎。這將發布到 "stop" 總線的通道。

    cherrypy.engine.stop()
    
  • 不要打電話 cherrypy.engine.block() .

  • 禁用內置HTTP服務器,因為它不會被使用。

    cherrypy.server.unsubscribe()
    
  • 禁用自動加載。通常其他框架對它反應不好,或者有時提供相同的特性。

    cherrypy.config.update({'engine.autoreload.on': False})
    
  • 禁用Cherrypy信號處理。這可能不需要,這取決于其他框架如何處理它們。

    cherrypy.engine.signals.subscribe()
    
  • 使用 "embedded" 環境配置方案。

    cherrypy.config.update({'environment': 'embedded'})
    

    基本上,這將禁用以下功能:

    • stdout日志記錄

    • 自動加載器

    • 配置檢查器

    • 頭登錄錯誤

    • 錯誤的回溯

    • 調度期間參數不匹配錯誤

    • 信號(嘆息、信號術語)

Tornado?

你可以使用 tornado HTTP服務器如下:

import cherrypy

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

if __name__ == '__main__':
    import tornado
    import tornado.httpserver
    import tornado.wsgi

    # our WSGI application
    wsgiapp = cherrypy.tree.mount(Root())

    # Disable the autoreload which won't play well
    cherrypy.config.update({'engine.autoreload.on': False})

    # let's not start the CherryPy HTTP server
    cherrypy.server.unsubscribe()

    # use CherryPy's signal handling
    cherrypy.engine.signals.subscribe()

    # Prevent CherryPy logs to be propagated
    # to the Tornado logger
    cherrypy.log.error_log.propagate = False

    # Run the engine but don't block on it
    cherrypy.engine.start()

    # Run thr tornado stack
    container = tornado.wsgi.WSGIContainer(wsgiapp)
    http_server = tornado.httpserver.HTTPServer(container)
    http_server.listen(8080)
    # Publish to the CherryPy engine as if
    # we were using its mainloop
    tornado.ioloop.PeriodicCallback(lambda: cherrypy.engine.publish('main'), 100).start()
    tornado.ioloop.IOLoop.instance().start()

扭曲的?

你可以使用 Twisted HTTP服務器如下:

import cherrypy

from twisted.web.wsgi import WSGIResource
from twisted.internet import reactor
from twisted.internet import task

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

# Create our WSGI app from the CherryPy application
wsgiapp = cherrypy.tree.mount(Root())

# Configure the CherryPy's app server
# Disable the autoreload which won't play well
cherrypy.config.update({'engine.autoreload.on': False})

# We will be using Twisted HTTP server so let's
# disable the CherryPy's HTTP server entirely
cherrypy.server.unsubscribe()

# If you'd rather use CherryPy's signal handler
# Uncomment the next line. I don't know how well this
# will play with Twisted however
#cherrypy.engine.signals.subscribe()

# Publish periodically onto the 'main' channel as the bus mainloop would do
task.LoopingCall(lambda: cherrypy.engine.publish('main')).start(0.1)

# Tie our app to Twisted
reactor.addSystemEventTrigger('after', 'startup', cherrypy.engine.start)
reactor.addSystemEventTrigger('before', 'shutdown', cherrypy.engine.exit)
resource = WSGIResource(reactor, reactor.getThreadPool(), wsgiapp)

注意我們如何將總線方法附加到Twisted自己的生命周期。

將該代碼保存到名為 cptw.py 運行如下:

$ twistd -n web --port 8080 --wsgi cptw.wsgiapp

UWSGI公司?

你可以使用 uwsgi HTTP服務器如下:

import cherrypy

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

cherrypy.config.update({'engine.autoreload.on': False})
cherrypy.server.unsubscribe()
cherrypy.engine.start()

wsgiapp = cherrypy.tree.mount(Root())

將其保存到名為 mymod.py 運行如下:

$ uwsgi --socket 127.0.0.1:8080 --protocol=http --wsgi-file mymod.py --callable wsgiapp

虛擬主機?

Cherrypy支持虛擬主機。它是通過一個調度器來實現的,調度器根據請求的域來定位適當的資源。

下面是一個簡單的例子:

import cherrypy

class Root(object):
    def __init__(self):
        self.app1 = App1()
        self.app2 = App2()

class App1(object):
    @cherrypy.expose
    def index(self):
        return "Hello world from app1"

class App2(object):
    @cherrypy.expose
    def index(self):
        return "Hello world from app2"

if __name__ == '__main__':
    hostmap = {
        'company.com:8080': '/app1',
        'home.net:8080': '/app2',
    }

    config = {
        'request.dispatch': cherrypy.dispatch.VirtualHost(**hostmap)
    }

    cherrypy.quickstart(Root(), '/', {'/': config})

在本例中,我們聲明兩個域及其端口:

  • 公司網址:8080

  • 主頁.net:8080

多虧了 cherrypy.dispatch.VirtualHost 調度器,當請求到達時,我們告訴Cherrypy要調度哪個應用程序。調度程序查找請求的域并調用相應的應用程序。

注解

要測試此示例,只需將以下規則添加到 hosts 文件:

127.0.0.1       company.com
127.0.0.1       home.net

反向代理?

阿帕奇?

Nginx?

nginx是一個快速、現代化的HTTP服務器,占用空間小。它是應用服務器(如Cherrypy)的反向代理的流行選擇。

本節不涵蓋nginx提供的所有功能。相反,它將簡單地為您提供一個基本的配置,這是一個很好的起點。

 1upstream apps {
 2   server 127.0.0.1:8080;
 3   server 127.0.0.1:8081;
 4}
 5
 6gzip_http_version 1.0;
 7gzip_proxied      any;
 8gzip_min_length   500;
 9gzip_disable      "MSIE [1-6]\.";
10gzip_types        text/plain text/xml text/css
11                  text/javascript
12                  application/javascript;
13
14server {
15   listen 80;
16   server_name  www.example.com;
17
18   access_log  /app/logs/www.example.com.log combined;
19   error_log  /app/logs/www.example.com.log;
20
21   location ^~ /static/  {
22      root /app/static/;
23   }
24
25   location / {
26      proxy_pass         http://apps;
27      proxy_redirect     off;
28      proxy_set_header   Host $host;
29      proxy_set_header   X-Real-IP $remote_addr;
30      proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
31      proxy_set_header   X-Forwarded-Host $server_name;
32   }
33}

編輯此配置以匹配您自己的路徑。然后,將此配置保存到下的文件中 /etc/nginx/conf.d/ (假設是Ubuntu)。文件名不相關。然后運行以下命令:

$ sudo service nginx stop
$ sudo service nginx start

希望這足以將點擊nginx前端的請求轉發到您的cherrypy應用程序。這個 upstream 塊定義CherryPy實例的地址。

它表明您可以在兩個應用服務器之間實現負載平衡。請參閱nginx文檔了解這是如何實現的。

upstream apps {
   server 127.0.0.1:8080;
   server 127.0.0.1:8081;
}

稍后,此塊用于定義反向代理部分。

現在,讓我們看看我們的應用程序:

import cherrypy

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

if __name__ == '__main__':
    cherrypy.config.update({
        'server.socket_port': 8080,
        'tools.proxy.on': True,
        'tools.proxy.base': 'http://www.example.com'
    })
    cherrypy.quickstart(Root())

如果您運行此代碼的兩個實例,一個在nginx部分定義的每個端口上,您將能夠通過nginx完成的負載平衡來訪問這兩個實例。

注意我們如何定義代理工具。它不是強制性的,只是為了讓Cherrypy請求知道真正的客戶地址。否則,它只知道nginx自己的地址。這在日志中最明顯。

這個 base 屬性應與 server_name nginx配置的部分。