Python Web 框架 - CherryPy

CherryPy is a pythonic, object-oriented web framework.
CherryPy allows developers to build web applications in much the same way they would build any other object-oriented Python program. This results in smaller source code developed in less time.
Reference:官网GitHub

安装

直接执行 $ pip install cherrypy
也可以从 GitHub 的 CherryPy 获取最新版的源代码并安装:

1
2
3
$ git clone https://github.com/cherrypy/cherrypy
$ cd cherrypy
$ python setup.py install

用测试用例检验是否安装成功,直接执行 $ python -m cherrypy.tutorial.tut01_helloworld
打开页面http://127.0.0.1:8080 或者http://localhost:8080 查看结果。
一旦成功,控制台也会显示log:
1
2
3
4
5
6
7
8
ENGINE Listening for SIGHUP.
ENGINE Listening for SIGTERM.
ENGINE Listening for SIGUSR1.
ENGINE Bus STARTING
ENGINE Started monitor thread 'Autoreloader'.
ENGINE Started monitor thread '_TimeoutMonitor'.
ENGINE Serving on http://127.0.0.1:8080
ENGINE Bus STARTED

Hello World 页面部署

1
2
3
4
5
6
7
8
9
import cherrypy

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

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

运行此程序后,控制台则会显示:

1
2
3
4
5
6
7
8
9
10
11
ENGINE Listening for SIGHUP.
ENGINE Listening for SIGTERM.
ENGINE Listening for SIGUSR1.
ENGINE Bus STARTING
CherryPy Checker:
The Application mounted at '' has an empty config.

ENGINE Started monitor thread 'Autoreloader'.
ENGINE Started monitor thread '_TimeoutMonitor'.
ENGINE Serving on http://127.0.0.1:8080
ENGINE Bus STARTED

前三行表示服务器会处理signal;第四行表示服务器的状态,此刻在启动阶段;第五、六行表示你的应用没有特别的配置;第八、九行表示服务器开始了一些内部的功能,倒数第二行表示已经准备好通信并且监听地址127.0.0.1:8080,最后一行表示现在你的应用已经开始运行,可以使用。
这里return为index页面的源码:
- 可以是一般字符串,比如 "Hello world!",页面直接显示字符串
- 可以是HTML格式的字符串,比如
1
2
3
4
5
6
7
8
9
10
11
12
13
14
"""
<html>
<head>
<script type="text/javascript" src="/xxx/xxx/xxx.js"></script>
<title>Write Your WebPage Title Here</title>
<link rel="stylesheet" type="text/css" href="/xxx/xxx/xxx.css" />
</head>
<body>
<div class="xxx">
Hello world!
</div>
</body>
</html>
"""

这里使用三对双引号,用来输入多行文本,之中的单号和双引号不用转义,其中的不可见字符比如。
- 可以是HTML文件 open('xxx/xxx/xxx.html')
注打开文件的时候可以传递参数
open("xxx.html").read().format(a=a, b=b, c=c, ...),则HTML文件中 {a:}; {b:}; {c:}; ...来表示相应的字符串。
或者 open("xxx.html").read() % (a, b, c, ...),HTML文件中 %s;%d;%f 等来表示相应位置的字符串。
原理:open("xxx.html").read() 返回一个string对象,传递参数同格式化输出。

cherrypy.quickstart() 启动单个应用,此函数至少需要一个参数,第一个必选一般为类名;第二个可选参数为应用访问的基础路径;第三个可选参数为应用配置。
如果要启动多个应用,则用 cherrypy.tree.mount(),此函数的参数和 cherrypy.quickstart() 一样,均为一个应用的类名,一个主机路径,一个配置。举例如下

1
2
3
4
5
cherrypy.tree.mount(A(), '/a', a_conf)
cherrypy.tree.mount(B(), '/b', b_conf)

cherrypy.engine.start()
cherrypy.engine.block()

不同的 URLs 对应不同的 functions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import random
import string
import cherrypy

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

@cherrypy.expose
def generate(self):
return ''.join(random.sample(string.hexdigits, 8))

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

运行此程序后,打开 http://localhost:8080 或者 http://localhost:8080/index 则会显示 Helloworld 界面(index 可以省略)。
而打开 http://localhost:8080/generate 则会运行 generate 函数并返回且显示一个 8 位随机字符串。
此外,开发者可以实现一个 default 方法,如果服务器找不到任何对应的方法时,将会调用 default 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import random
import string
import cherrypy

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

@cherrypy.expose
def default(self, url):
return ''.join(random.sample(string.hexdigits, 8))

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

运行此程序后,打开http://localhost:8080/xxx 则会运行default函数并返回且显示一个8位随机字符串,这里xxx为任意网址。
注意,default函数必须有两个参数,url会传入用户所访问的网址,即 url=xxx

通过 URL 传递参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import random
import string
import cherrypy

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

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

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

运行此程序后,打开 http://localhost:8080/generate?length=16 ,
则地址栏中的 16 会被从客户端传递到服务器端,作为 generate 的参数 length 的值,其结果是返回且显示一个长度为 16 的随机字符串。
如果直接访问 http://localhost:8080/generate 则参数为缺省值 length=8。
如果并未设置缺省值也没有通过 url 传递参数则会报错。

提交表单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import random
import string
import cherrypy

class StringGenerator(object):
@cherrypy.expose
def index(self):
return """<html>
<head></head>
<body>
<form method="get" action="generate">
<input type="text" value="8" name="length" />
<button type="submit">Give it now!</button>
</form>
</body>
</html>"""

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

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

运行此程序后,打开 http://localhost:8080/ ,填写表格并提交。
触发 submit 时候,根据 form 中定义的 action 的名字寻找对应的 function,并将表单元素提交的内容作为 name 属性所对应的参数传递。

Session

一个应用经常需要追踪用户的行为,一个常用的做法是使用 session 暂存数据,并且在客户端服务器通信中保持。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import random
import string
import cherrypy

class StringGenerator(object):
@cherrypy.expose
def index(self):
return """<html>
<head></head>
<body>
<form method="get" action="generate">
<input type="text" value="8" name="length" />
<button type="submit">Give it now!</button>
</form>
</body>
</html>"""

@cherrypy.expose
def generate(self, length=8):
some_string = ''.join(random.sample(string.hexdigits, int(length)))
cherrypy.session['mystring'] = some_string
return some_string

@cherrypy.expose
def display(self):
return cherrypy.session['mystring']

if __name__ == '__main__':
conf = {
'/': {
'tools.sessions.on': True
}
}
cherrypy.quickstart(StringGenerator(), '/', conf)

运行此程序后,打开http://localhost:8080/generate 生成一个随机字符串并存入session的'mystring'字段之中。
打开http://localhost:8080/display 展示session中存储的字符串。
CherryPy会存储session的信息在进程的内存中。
注意此时要修改.conf文件。
1
2
[/]
tools.sessions.on = True

Cookies

CherryPy 使用 Python 模块 Cookie,Cookie.SimpleCookie 对象来处理 cookie
要将 cookie 发送至浏览器,使用 cherrypy.response.cookie[key] = value
要提取浏览器的 cookie,使用 cherrypy.request.cookie[key]
要删除 cookie(在客户端),必须将 cookie 的过期时间设置为 0:cherrypy.response.cookie[key]['expires'] = 0

允许文件上传、下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import os
import os.path
import cherrypy
from cherrypy.lib import static

localDir = os.path.dirname(__file__)
absDir = os.path.join(os.getcwd(), localDir)

class FileDemo(object):
@cherrypy.expose
def index(self):
return """
<html><body>
<h2>Upload a file</h2>
<form action="upload" method="post" enctype="multipart/form-data">
filename: <input type="file" name="myFile" /><br/>
<input type="submit" />
</form>
<h2>Download a file</h2>
<a href='download'>This one</a>
</body></html>
"""

@cherrypy.expose
def upload(self, myFile):
out = """<html>
<body>
myFile length: %s<br />
myFile filename: %s<br />
myFile mime-type: %s
</body>
</html>"""
size = 0
while True:
data = myFile.file.read(8192)
if not data:
break
size += len(data)
return out % (size, myFile.filename, myFile.content_type)

@cherrypy.expose
def download(self):
path = os.path.join(absDir, 'pdf_file.pdf')
return static.serve_file(path, 'application/x-download', 'attachment', os.path.basename(path))

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

上传
当客户端上传一个文件到 CherryPy 应用时,CherryPy 将会将文件作为一个参数传给方法(upload (self, myFile))。
此参数有一个 file 属性,可以用于操作临时传入的文件,如果想要永久的保留文件,则需要用 read () 函数读取 myFile.file 的数据并且写入到某个文件中。
下载
使用响应类型 “application/x-download”,可以告诉浏览器,应该将资源下载到用户的机器上,而不是显示。
serve_file(path, content_type=None, disposition=None, name=None, debug=False) 函数:第一个参数为文件路径;第二个参数为内容类型;第三个参数为 Content-Disposition 信息(Content-Disposition 就是当用户想把请求所得的内容存为一个文件的时候提供一个默认的文件名),如果不为空,则 Content-Disposition header 会被设置为 "<disposition>; filename=<name>",如果 name 为空,则 name 被设置为文件的 basename。

以流形式处理请求主体

CherryPy 收到 HTTP 请求后,对于底层信心进行处理后将请求传给页面处理部分,这里就会生成请求主体 (response body).
ChereyPy 支持多种类型的请求主体,如字符串、字符串列表或者文件;同样,也可以通过 yield 内容的方式形成 stream。
默认是关闭流输出的,因为这更加安全也更容易处理;如果要开启这个功能,需要对 session 会话有所了解。
一般请求的处理过程中,HTTP 服务器是一次性将整个请求接受、一次性将整个结果返回。
这对于简单的网页或者静态内容比较合适,因为请求、代码等都可以随时改变。
而流输出中程序不是一次性的返回数据,而是返回生成器,再通过生成器不断的以流的形式获取数据。
使用 response.stream = true 并结合 yield,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import os.path
import cherrypy

class HelloWorld(object):
@cherrypy.expose
def index(self):
yield "Hello "
yield "world!"

if __name__ == '__main__':
conf = {
'/': {
'response.stream': True
}
}
cherrypy.quickstart(HelloWorld(), '/', conf)

调用网站资源

网络应用经常会调用一些静态资源,比如 JavaScriptCSS 或 image 等资源。
在网站的根目录下新建文件夹 public,在 public 中可以自己新建文件夹 js,css,image 等子文件夹,并在相应文件夹中存放相应的资源。
在 HTML 调用资源的地方写入:
<script type="text/javascript" src="/static/js/xxx.js"></script>
<link rel="stylesheet" type="text/css" href="/static/css/xxx.css" />
<img src="/static/image/xxx.jpg" alt="xxx" style="width:123px;height:123px;">
并且修改.conf 文件。

1
2
3
4
5
6
[/]
tools.staticdir.root = os.path.abspath(os.getcwd())

[/static]
tools.staticdir.on = True
tools.staticdir.dir = "public"

对于独立文件资源:
1
2
3
[/style.css]
tools.staticfile.on = True
tools.staticfile.filename = filepath

REST

客户端除了浏览器之外,还有其他一些形式比如 python,iOS,Android 等。
RESTful 可以通过一套统一的接口为他们提供服务。
而且对于很多平台来说,不需要有显式的前端,只需要一套提供服务的接口。
REST 用 URL 定位资源,用 HTTP 动词(GET,POST,PUT,DELETE 等)描述操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import random
import string
import cherrypy

@cherrypy.expose
class StringGeneratorWebService(object):

@cherrypy.tools.accept(media='text/plain')
def GET(self):
return cherrypy.session['mystring']

def POST(self, length=8):
some_string = ''.join(random.sample(string.hexdigits, int(length)))
cherrypy.session['mystring'] = some_string
return some_string

def PUT(self, another_string):
cherrypy.session['mystring'] = another_string

def DELETE(self):
cherrypy.session.pop('mystring', None)

if __name__ == '__main__':
conf = {
'/': {
'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
'tools.sessions.on': True,
'tools.response_headers.on': True,
'tools.response_headers.headers': [('Content-Type', 'text/plain')],
}
}
cherrypy.quickstart(StringGeneratorWebService(), '/', conf)

运行此程序后,可以使用python的requests库来进行交互。
这里通过@cherrypy.expose装饰器一次性暴露StringGeneratorWebService类的所有方法。
该应用不再是以<URL,函数>匹配的方式进行工作,而是在配置里面创建了cherrypy.dispatch.MethodDispatcher实例,用于自动匹配。
对于get函数强制response的contenttype为text/plain。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> import requests
>>> s = requests.Session()
>>> r = s.get('http://127.0.0.1:8080/')
>>> r.status_code
500 # 因为session['mystring']为空,所以产生服务器错误
>>> r = s.post('http://127.0.0.1:8080/')
>>> r.status_code, r.text
(200, u'04A92138') # post提交一个长度为8的随机字符串并存入session中
>>> r = s.get('http://127.0.0.1:8080/')
>>> r.status_code, r.text
(200, u'04A92138') # get获取session中所存的字符串
>>> r = s.get('http://127.0.0.1:8080/', headers={'Accept': 'application/json'})
>>> r.status_code
406 # 因为提出请求,将获取所生成的字符串作为一个JSON格式返回,格式不符所以返回406错误
# 406 Not Acceptable 请求的资源的内容特性无法满足请求头中的条件,因而无法生成响应实体,该请求不可接受。
>>> r = s.put('http://127.0.0.1:8080/', params={'another_string': 'hello'})
>>> r = s.get('http://127.0.0.1:8080/')
>>> r.status_code, r.text
(200, u'hello') # put更新session中所存的字符串
>>> r = s.delete('http://127.0.0.1:8080/')
>>> r = s.get('http://127.0.0.1:8080/')
>>> r.status_code
500 # delete删除session中的字符串,所以产生服务器错误

AJAX

AJAX = Asynchronous JavaScript and XML(异步的 JavaScript 和 XML)。
AJAX 是在不重新加载整个页面的情况下与服务器交换数据并更新部分网页的技术。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import os, os.path
import random
import string
import cherrypy

class StringGenerator(object):
@cherrypy.expose
def index(self):
return '''<!DOCTYPE html>
<html>
<head>
<style>
#the-string {
display: none;
}
</style>
<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {

$("#generate-string").click(function(e) {
$.ajax({
type: "POST",
url: "/generator",
data: {"length": $("input[name='length']").val()}
})
e.preventDefault();
});

$("#replace-string").click(function(e) {
$.ajax({
type: "PUT",
url: "/generator",
data: {"another_string": $("#the-string input").val()}
})
.done(function() {
alert("Replaced!");
});
e.preventDefault();
});

$("#delete-string").click(function(e) {
$.ajax({
type: "DELETE",
url: "/generator"
})
.done(function() {
$("#the-string").hide();
});
e.preventDefault();
});

});
</script>
</head>
<body>
<input type="text" value="8" name="length"/>
<button id="generate-string">Give it now!</button>
<div id="the-string">
<input type="text" />
<button id="replace-string">Replace</button>
<button id="delete-string">Delete it</button>
</div>
</body>
</html>
'''

@cherrypy.expose
class StringGeneratorWebService(object):

@cherrypy.tools.accept(media='text/plain')
def GET(self):
return cherrypy.session['mystring']

def POST(self, length=8):
some_string = ''.join(random.sample(string.hexdigits, int(length)))
cherrypy.session['mystring'] = some_string
return some_string

def PUT(self, another_string):
cherrypy.session['mystring'] = another_string

def DELETE(self):
cherrypy.session.pop('mystring', None)

if __name__ == '__main__':
conf = {
'/': {
'tools.sessions.on': True,
'tools.staticdir.root': os.path.abspath(os.getcwd())
},
'/generator': {
'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
'tools.response_headers.on': True,
'tools.response_headers.headers': [('Content-Type', 'text/plain')],
},
}
webapp = StringGenerator()
webapp.generator = StringGeneratorWebService()
cherrypy.quickstart(webapp, '/', conf)

运行此程序后,可以使用按钮触发ajax结合jQuery来进行交互。
其中:
1
2
3
4
5
6
7
8
9
10
11
12
$("#generate-string").click(function(e) {
$.ajax({
type: "POST",
url: "/generator",
data: {"length": $("input[name='length']").val()}
})
.done(function(string) {
$("#the-string").show();
$("#the-string input").val(string);
});
e.preventDefault();
});

表示当id为generate-string的按钮被点击后,执行POST命令,目标url是generator,以JSON方式传参数length,length的值为名字等于length的input组件的值。完成POST后服务器返回string,并且将id为the-string的组件显示出来,将其input组件的值赋值为string。
1
2
3
4
5
6
7
8
9
10
11
$("#replace-string").click(function(e) {
$.ajax({
type: "PUT",
url: "/generator",
data: {"another_string": $("#the-string input").val()}
})
.done(function() {
alert("Replaced!");
});
e.preventDefault();
});

表示当id为replace-string的按钮被点击后,执行PUT命令,目标url是generator,以JSON方式传参数another_string,another_string的值为id为the-string的组件中的input组件的值。完成PUT后弹出警告框"Replaced"。
1
2
3
4
5
6
7
8
9
10
$("#delete-string").click(function(e) {
$.ajax({
type: "DELETE",
url: "/generator"
})
.done(function() {
$("#the-string").hide();
});
e.preventDefault();
});

表示当id为delete-string的按钮被点击后,执行DELETE命令,目标url是generator,完成DELETE后将id为the-string的组件隐藏。

数据库存储

这里以 SQLite 数据库为例,演示如何将网页数据存入数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import os, os.path
import random
import sqlite3
import string
import time
import cherrypy

DB_STRING = "my.db"

class StringGenerator(object):
@cherrypy.expose
def index(self):
return '''<!DOCTYPE html>
<html>
<head>
<style>
#the-string {
display: none;
}
</style>
<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {

$("#generate-string").click(function(e) {
$.ajax({
type: "POST",
url: "/generator",
data: {"length": $("input[name='length']").val()}
})
e.preventDefault();
});

$("#replace-string").click(function(e) {
$.ajax({
type: "PUT",
url: "/generator",
data: {"another_string": $("#the-string input").val()}
})
.done(function() {
alert("Replaced!");
});
e.preventDefault();
});

$("#delete-string").click(function(e) {
$.ajax({
type: "DELETE",
url: "/generator"
})
.done(function() {
$("#the-string").hide();
});
e.preventDefault();
});

});
</script>
</head>
<body>
<input type="text" value="8" name="length"/>
<button id="generate-string">Give it now!</button>
<div id="the-string">
<input type="text" />
<button id="replace-string">Replace</button>
<button id="delete-string">Delete it</button>
</div>
</body>
</html>
'''

@cherrypy.expose
class StringGeneratorWebService(object):

@cherrypy.tools.accept(media='text/plain')
def GET(self):
with sqlite3.connect(DB_STRING) as c:
cherrypy.session['ts'] = time.time()
r = c.execute("SELECT value FROM user_string WHERE session_id=?",
[cherrypy.session.id])
return r.fetchone()

def POST(self, length=8):
some_string = ''.join(random.sample(string.hexdigits, int(length)))
with sqlite3.connect(DB_STRING) as c:
cherrypy.session['ts'] = time.time()
c.execute("INSERT INTO user_string VALUES (?, ?)",
[cherrypy.session.id, some_string])
return some_string

def PUT(self, another_string):
with sqlite3.connect(DB_STRING) as c:
cherrypy.session['ts'] = time.time()
c.execute("UPDATE user_string SET value=? WHERE session_id=?",
[another_string, cherrypy.session.id])

def DELETE(self):
cherrypy.session.pop('ts', None)
with sqlite3.connect(DB_STRING) as c:
c.execute("DELETE FROM user_string WHERE session_id=?",
[cherrypy.session.id])

def setup_database():
"""
Create the `user_string` table in the database
on server startup
"""
with sqlite3.connect(DB_STRING) as con:
con.execute("CREATE TABLE user_string (session_id, value)")

def cleanup_database():
"""
Destroy the `user_string` table from the database
on server shutdown.
"""
with sqlite3.connect(DB_STRING) as con:
con.execute("DROP TABLE user_string")

if __name__ == '__main__':
conf = {
'/': {
'tools.sessions.on': True,
'tools.staticdir.root': os.path.abspath(os.getcwd())
},
'/generator': {
'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
'tools.response_headers.on': True,
'tools.response_headers.headers': [('Content-Type', 'text/plain')],
},
}

cherrypy.engine.subscribe('start', setup_database)
cherrypy.engine.subscribe('stop', cleanup_database)

webapp = StringGenerator()
webapp.generator = StringGeneratorWebService()
cherrypy.quickstart(webapp, '/', conf)

运行此程序后,可以使用按钮触发ajax来进行交互。
大部分功能与上一个例子相同,这里增加了将session中的数据存入数据库的功能。
其中 cherrypy.engine.subscribe('start', setup_database)cherrypy.engine.subscribe('stop', cleanup_database) 表示setup_database函数和cleanup_database函数被注册到服务器,当服务器启动和停止时调用。
setup_database函数新建一个名为user_string的表格,cleanup_database函数删除user_string表格。

日志记录

CherryPy 会记录所有的 requests 和协议错误。
应用也可以自己配置日志记录功能,调用 cherrypy.log()
如果在配置中配置以下内容,则所有的 log 会被记录到文件:
- log.access_file 记录 requests 情况
- log.error_file 记录其他的情况

修改.conf 文件。

1
2
3
4
[/]
log.screen = False, #用于取消console的logging
log.access_file = "access.log",
log.error_file = "error.log",

服务器配置

全局配置 (global)

HTTP 配置

1
2
3
4
[global]
server.socket_host = "127.0.0.1"
server.socket_port = 8080
server.thread_pool = 10

HTTPS配置
1
2
3
4
5
6
7
[global]
server.socket_host = "0.0.0.0"
server.socket_port = 443
server.thread_pool = 10
server.ssl_module = 'builtin'
server.ssl_certificate = "cert.pem"
server.ssl_private_key = "privkey.pem"

可以用OpenSSL工具生成自签名的证书,方法如下:

  1. $ openssl genrsa -out privkey.pem 2048 #用于生成私钥
  2. 修改 OpenSSL.cnf 配置文件(最好不修改此目录下的文件,而是拷贝一份至你的生成证书的目录)
  3. 找到 [v3_ca] 在其下方加入并保存:
    1
    2
    3
    subjectAltName = "IP:server的ip"
    basicConstraints = CA:FALSE
    keyUsage = digitalSignature, keyEncipherment
  4. $ openssl req -new -x509 -days 365 -key privkey.pem -out cert.pem -config openssl.cnf #用于生成证书
    有效期为 365 天,按照提示写入个人信息(国、省、市、机构、组织、server 的 ip、邮箱)。
  5. cert.pem 的后缀改为 cer 就可以作为证书使用了。

网站 Gzip

Gzip 开启以后会将输出到用户浏览器的数据进行压缩的处理,这样就会减小通过网络传输的数据量,提高浏览的速度。
修改.conf 文件开启压缩功能。

1
2
[/]
tools.gzip.on: True

其他配置

也可以将某些字符串存入配置文件,供各种函数访问。
修改.conf 文件。

1
2
[abc]
def = "ghi"

在函数中通过 jklmn = cherrypy.request.app.config['abc']['def'] 调用,此时jklmn变量被赋值"ghi",str类型。

处理 JSON

CherryPy 内置支持了对 JSON 编码的请求或响应的解码支持。

解码 request

自动解码 JSON 请求的内容:

1
2
3
4
@cherrypy.expose
@cherrypy.tools.json_in()
def index(self):
data = cherrypy.request.json

附加在请求的JSON属性包含解码内容。

编码 response

使用 JSON 自动编码 response 的内容:

1
2
3
4
@cherrypy.expose
@cherrypy.tools.json_out()
def index(self):
return {'key': 'value'}

CherryPy将使用JSON对你的页面处理程序返回的任何内容进行编码,并非所有类型的对象都可以被编码。

认证

CherryPy 提供了两种非常简单的身份验证机制。他们最常见的触发方式是触发浏览器弹出窗口向用户询问他们的名字和密码。

Basic

Basic 身份验证是最简单的验证方式,但它不是一个安全的身份验证,因为用户的凭证被嵌入到请求中。不建议使用它,除非你在 SSL 或封闭的网络中运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from cherrypy.lib import auth_basic

USERS = {'id': 'password'}

def validate_password(realm, username, password):
if username in USERS and USERS[username] == password:
return True
return False

conf = {
'/protected/area': {
'tools.auth_basic.on': True,
'tools.auth_basic.realm': 'localhost',
'tools.auth_basic.checkpassword': validate_password
}
}

cherrypy.quickstart(myapp, '/', conf)

必须提供一个将有CherryPy调用的函数,解码从请求中传递的用户名和密码。
该功能可以从任何来源读取数据:文件,数据库,内存等。

Digest

Digest 认证的不同之处在于,凭证没有携带在请求中,因此比 Basic 更安全。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from cherrypy.lib import auth_digest

USERS = {'id': 'password'}

conf = {
'/protected/area': {
'tools.auth_digest.on': True,
'tools.auth_digest.realm': 'localhost',
'tools.auth_digest.get_ha1': auth_digest.get_ha1_dict_plain(USERS),
'tools.auth_digest.key': 'a565c27146791cfb'
}
}

cherrypy.quickstart(myapp, '/', conf)

网站图标

CherryPy 提供自己的红色 cherrypy 作为默认图标。可以用以下方式提供自己的图标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import cherrypy

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

if __name__ == '__main__':
cherrypy.quickstart(HelloWorld(), '/',
{
'/favicon.ico':
{
'tools.staticfile.on': True,
'tools.staticfile.filename': '/path/to/myfavicon.ico'
}
}
)

也可以使用文件进行配置:
1
2
3
[/favicon.ico]
tools.staticfile.on: True
tools.staticfile.filename: "/path/to/myfavicon.ico"

1
2
3
4
5
6
7
8
9
import cherrypy

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

if __name__ == '__main__':
cherrypy.quickstart(HelloWorld(), '/', app.conf)

设置页面别名

1
2
3
4
5
6
7
8
9
10
11
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())

其中 @cherrypy.expose(['generer', 'generar']) 设置了别名,所以下面三个页面效果是一样:/generate;/generer (French);/generar (Spanish)

请求超时

CherryPy 包含 3 个时间相关的属性:
- response.time:当请求开始的时候,记录 time.time ()
- response.timeout:请求可以运行的时间,默认 300s
- response.timed_out:是否激活超时机制 (默认 False)

可以通过 response.check_timeout 来检查是否超时,在激活超时机制的情况下,一旦超时则触发 TimeoutError 异常。
默认会通过 cherrypy.engine.timeout_monitor 监控所有请求是否超时,如果要关闭:

1
2
[global]
engine.timeout_monitor.on: False

或者
1
cherrypy.engine.timeout_monitor.unsubscribe()

默认超时监控是1分钟1次,改成1小时1次:
1
2
[global]
engine.timeout_monitor.frequency: 60 * 60

信号处理

对于信号处理,有一个 cherrypy.engine.signal_handler 插件,它是被 cherrypy.quickstart () 自动调用的。
如果要手动进行信号处理,则可以调用 tree.mount()engine.start()engine.block()
记得启动引擎前调用 engine.signals.subscribe()