部署

Cherrypy独立存在,但作为一个应用服务器,它通常位于共享或复杂的环境中。因此,在反向代理之后运行cherrypy或使用其他服务器托管应用程序并不少见。

注解

多年来,Cherrypy的服务器已经足够可靠和快速。如果你接收到的流量是平均的,那么它自己就能做得很好。尽管如此,将静态内容的服务委托给更强大的服务器(如 nginx 或cdn。

作为守护进程运行

Cherrypy允许您使用传统的双叉轻松地将当前流程与父环境分离:

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

注解

这个 engine plugin 仅在提供 fork() .

如果复刻子进程中发生启动错误,则父进程返回的代码仍然为0。初始守护进程中的错误仍然返回正确的退出代码,但fork之后的错误不会返回。因此,如果使用此插件进行守护,则不要将返回代码用作进程是否完全启动的准确指示器。实际上,该返回代码仅指示进程是否成功完成了第一个复刻。

该插件采用可选参数重定向标准流: stdinstdoutstderr .默认情况下,这些都重定向到 /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附带了一个非常可靠和足够快的HTTP服务器,但是您可能希望将您的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配置的部分。