部署¶
Cherrypy独立存在,但作为一个应用服务器,它通常位于共享或复杂的环境中。因此,在反向代理之后运行cherrypy或使用其他服务器托管应用程序并不少见。
注解
多年来,Cherrypy的服务器已经足够可靠和快速。如果你接收到的流量是平均的,那么它自己就能做得很好。尽管如此,将静态内容的服务委托给更强大的服务器(如 nginx 或cdn。
目录
作为守护进程运行¶
Cherrypy允许您使用传统的双叉轻松地将当前流程与父环境分离:
from cherrypy.process.plugins import Daemonizer
d = Daemonizer(cherrypy.engine)
d.subscribe()
注解
这个 engine plugin 仅在提供 fork()
.
如果复刻子进程中发生启动错误,则父进程返回的代码仍然为0。初始守护进程中的错误仍然返回正确的退出代码,但fork之后的错误不会返回。因此,如果使用此插件进行守护,则不要将返回代码用作进程是否完全启动的准确指示器。实际上,该返回代码仅指示进程是否成功完成了第一个复刻。
该插件采用可选参数重定向标准流: stdin
, stdout
和 stderr
.默认情况下,这些都重定向到 /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连接。这样可以保证网络流量的安全。方法如下。
生成私钥。我们将使用openssl并遵循 OpenSSL Keys HOWTO :
$ openssl genrsa -out privkey.pem 2048
您可以创建需要密码才能使用的密钥,也可以创建不带密码的密钥。用密码保护您的私钥要安全得多,但要求您每次使用密钥时都输入密码。例如,在启动或重新启动Cherrypy服务器时,可能需要输入密码。这可能可行,也可能不可行,具体取决于您的设置。
如果需要密码,请添加 -aes128
, -aes192
或 -aes256
切换到上面的命令。您不应该使用任何DES、3DES或SEED算法来保护您的密码,因为它们是不安全的。
SSL实验室建议使用2048位RSA密钥来实现安全性(请参阅末尾的参考部分)。
生成证书。我们将使用openssl并遵循 OpenSSL Certificates HOWTO .让我们从自我签名的测试证书开始:
$ openssl req -new -x509 -days 365 -key privkey.pem -out cert.pem
然后,OpenSSL将向您提出一系列问题。您可以输入任何适用的值,或将大多数字段留空。唯一的领域是你 must 填充是“公用名”:输入用于访问网站的主机名。如果您只是在自己的计算机上创建要测试的证书,并且通过在浏览器中键入“localhost”来访问服务器,请输入公用名“localhost”。
决定是使用python的内置ssl库,还是使用pyopenssl库。Cherrypy也支持。
Built-in. 要使用python的内置ssl,请在cherrypy配置中添加以下行:
cherrypy.server.ssl_module = 'builtin'
pyopenssl公司 .因为在第一次创建cherrypy时,python没有内置的ssl库,所以默认设置是使用pyopenssl。要使用它,您需要安装它(我们可以建议您安装 cython 第一个):
$ pip install cython, pyOpenSSL
在cherrypy配置中添加以下行以指向证书文件:
cherrypy.server.ssl_certificate = "cert.pem"
cherrypy.server.ssl_private_key = "privkey.pem"
如果手头有证书链,还可以指定它:
cherrypy.server.ssl_certificate_chain = "certchain.perm"
正常启动Cherrypy服务器。请注意,如果在本地调试和/或使用自签名证书,浏览器可能会向您显示安全警告。
WSGi服务器¶
嵌入到另一个WSGi框架中¶
虽然Cherrypy附带了一个非常可靠和足够快的HTTP服务器,但是您可能希望将您的Cherrypy应用程序集成到不同的框架中。为此,我们将从中定义的wsgi接口中获益。 PEP 333 和 PEP 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配置的部分。