Web Server Gateway Interface
) CherryPy
是一个极简主义风格的 Pythonic
Web框架.
前段时间使用了下,用于接收VBA程序的HTTP(Hypertext Transfer Protocol
)请求,这里做个学习笔记。
Web Server Gateway Interface
) 早期Python应用程序通常是为 CGI
, FastCGI
, mod_python
中的一个而设计,甚至是为特定Web服务器的自定义的API接口而设计.
SWGI
是Python专用的 Web服务器
和 Web应用程序或框架
之间的一种简单而通用的接口协议,是Python web程序开发的标准,具体查看 PEP 0333 内容(Python3.x 查看PEP 3333). WSGI是基于现存的CGI标准而设计.
常用的Python Web框架(兼容SWGI)有(http://en.wikipedia.org/wiki/Category:Python_web_application_frameworks):
- BlueBream
CherryPy
Django
Flask
- Grok
- Nagare
- Nevow
- Pyjamas
- Pylons
- Pyramid
- Quixote
- Spyce
- TACTIC
Tornado
- TurboGears
- TwistedWeb
- Webware
web.py
web2py
Zope
类似的,在其他语言进行Web开发也有相应的接口协议,如:
Rack
– Ruby web server interfacePSGI
– Perl Web Server Gateway InterfaceSCGI
– Simple Common Gateway InterfaceJSGI
– JavaScript web server gateway interface
- 可靠、兼容HTTP/1.1、支持WSGI线程池(
thread-pooled
)的Web服务器- 同时运行多个HTTP服务器(通过不同端口)
- 为开发和部署提供强大的配置文件系统
- 灵活的插件系统
- 内置工具包括缓存、编码、会话、授权和静态内容等
- 可定制
- 内置性能分析和测试功能
- 可以运行在Python 2.5+, 3.1+, PyPy, Jython和Android等环境
CherryPy
在设计风格方面,尽量的保持Pythonic,具有鲜明的Python风格;既可以作为一个Web框架,也可以作为一个普通模块使用。
一个简单的范例:
import cherrypy class HelloWorld(object): def index(self): return "Hello World!" index.exposed = True cherrypy.quickstart(HelloWorld())
或者:
import cherrypy class HelloWorld(object): @cherrypy.expose def index(self): return "Hello World!" cherrypy.quickstart(HelloWorld())
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())
启动服务器后,函数 generate
对应处理的URL是 http://localhost:8080/generate
,对于URL:
http://
: 指明使用的协议是HTTP协议localhost:8080
: 这是服务器地址/generate
: 这是URL的path
import random import string import cherrypy class StringGenerator(object): @cherrypy.expose def index(self): return "Hello world!" @cherrypy.expose def generate(self, length=8, prefix=""): return preix + ''.join(random.sample(string.hexdigits, int(length))) if __name__ == '__main__': cherrypy.quickstart(StringGenerator())
对应的URL是 http://localhost:8080/generate?length=16&prefix=Return
, 在 ?
之后的叫 query_string
, 会被解析成 (key,value)
形式,用 &
分隔。
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())
会将表单 GET
动作会被解析为 query-string (key, value)对
,然后调用对应的函数。
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)
通过提供 配置文件
激活 会话功能
,从而跟踪用户状态,对于 cherrypy.quickstart
:
- quickstart(root=None, script_name='', config=None) : Mount the given root, start the builtin server (and engine), then block.
- root: 用于接受/处理HTTP请求的类的实例
- script_name: 一个表示挂载点的字符串,应该以
/
开头并不可以以/
结束- config: 一个作为配置文件的dict结构
创建文件 : ./public/css/style.css
, 不需要创建 ./static
import os, os.path import random import string import cherrypy class StringGenerator(object): @cherrypy.expose def index(self): return """<html> <head> <link href="http://lesliezhu.github.com/static/css/style.css" rel="stylesheet"> </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, 'tools.staticdir.root': os.path.abspath(os.getcwd()) }, '/static': { 'tools.staticdir.on': True, 'tools.staticdir.dir': './public' } } cherrypy.quickstart(StringGenerator(), '/', conf)
对于静态内容,处于安全考虑,进行了一个映射,将 /static
变成了 /public
,这样用户就只能访问web服务器访问的静态内容,无法访问其他文件,因为不知道实际的路径。
如果没有设置映射, cherrypy会检查,如:
CherryPy Checker: tools.staticdir.dir is not set. section: [/static] root: '/path/to/cherrypy' dir: None
目前在三种主流的Web服务实现方案中,因为 REST模式
与复杂的 SOAP
和 XML-RPC
相比更加简洁,越来越多的web服务开始采用REST风格设计和实现。
REST是 设计风格
而不是标准。REST通常基于使用HTTP,URI,和XML以及HTML这些现有的广泛流行的协议和标准:
- 资源是由URI来指定。
- 对资源的操作包括获取、创建、修改和删除资源,这些操作正好对应HTTP协议提供的
GET
、POST
、PUT
和DELETE
方法。- 通过操作资源的表现形式来操作资源。
- 资源的表现形式则是XML或者HTML,取决于读者是机器还是人,是消费web服务的客户软件还是web浏览器。当然也可以是任何其他的格式。
REST
是 含状态传输
,应该注意区别应用的状态和连接协议的状态。HTTP连接是 无状态
的(也就是不记录每个连接的信息),而REST传输会包含 应用的所有状态
信息,因此可以大幅降低对HTTP连接的 重复请求
资源消耗。
举例:
import random import string import cherrypy class StringGeneratorWebService(object): exposed = True @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)
一方面,通过 exposed = True
来一次性设置,并且不在是进行 函数匹配
的方式进行工作,在配置里面设置了 cherrypy.dispatch.MethodDispatcher
。
另外,为了测试效果,不再使用浏览器,而是使用 requests
模块:
$ pip install requests $ python >>> import requests >>> s = requests.Session() >>> r = s.get('http://127.0.0.1:8080/') >>> r.status_code 500 >>> r = s.post('http://127.0.0.1:8080/') >>> r.status_code, r.text (200, u'04A92138') >>> r = s.get('http://127.0.0.1:8080/') >>> r.status_code, r.text (200, u'04A92138') >>> r = s.get('http://127.0.0.1:8080/', headers={'Accept': 'application/json'}) >>> r.status_code 406 >>> 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') >>> r = s.delete('http://127.0.0.1:8080/') >>> r = s.get('http://127.0.0.1:8080/') >>> r.status_code 500
近年来,web程序已经不再满足于 提交HTML表单,然后刷新整个页面
的工作方式,而是在客户端程序里面决定需要刷新的部分资源,从而不需要从服务器端刷新 整个页面
,提高性能,降低消耗。
一个使用 jQuery框架
的例子:
import os, os.path import random import string import cherrypy class StringGenerator(object): @cherrypy.expose def index(self): return file('index.html') class StringGeneratorWebService(object): exposed = True @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')], }, '/static': { 'tools.staticdir.on': True, 'tools.staticdir.dir': './public' } } webapp = StringGenerator() webapp.generator = StringGeneratorWebService() cherrypy.quickstart(webapp, '/', conf)
这里看不出有什么区别,主要是 index.html
里面采用了 ajax
:
<!DOCTYPE html> <html> <head> <link href="http://lesliezhu.github.com/static/css/style.css" rel="stylesheet"> <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) { $.post("/generator", {"length": $("input[name='length']").val()}) .done(function(string) { $("#the-string").show(); $("#the-string input").val(string); }); e.preventDefault(); }); $("#replace-string").click(function(e) { $.ajax({ type: "PUT", url: "/generator", data: {"another_string": $("#the-string").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>
通过 ajax
,对于指定的部分资源进行更新,其它元素不更新,这样整个页面就不需要反复刷新。
会话信息
只能保持在内存中,如果要长期保存数据,可选的存储数据库:
relational
: PostgreSQL, SQLite, MariaDB, Firebirdcolumn-oriented
: HBase, Cassandrakey-store
: redis, memcacheddocument oriented
: Couchdb, MongoDBgraph-oriented
: neo4j
一个操作 sqlite关系数据库
的例子:
import os, os.path import random import sqlite3 import string import cherrypy DB_STRING = "my.db" class StringGenerator(object): @cherrypy.expose def index(self): return file('index.html') class StringGeneratorWebService(object): exposed = True @cherrypy.tools.accept(media='text/plain') def GET(self): with sqlite3.connect(DB_STRING) as c: c.execute("SELECT value FROM user_string WHERE session_id=?", [cherrypy.session.id]) return c.fetchone() def POST(self, length=8): some_string = ''.join(random.sample(string.hexdigits, int(length))) with sqlite3.connect(DB_STRING) as c: 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: c.execute("UPDATE user_string SET value=? WHERE session_id=?", [another_string, cherrypy.session.id]) def DELETE(self): 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')], }, '/static': { 'tools.staticdir.on': True, 'tools.staticdir.dir': './public' } } cherrypy.engine.subscribe('start', setup_database) cherrypy.engine.subscribe('stop', cleanup_database) webapp = StringGenerator() webapp.generator = StringGeneratorWebService() cherrypy.quickstart(webapp, '/', conf)
这里注册了 setup_database
和 cleanup_database
函数作为启动和退出的初始化、清理工作。
CherryPy
健壮的架构设计为更好的维护代码、更高的灵活性提供了大量机制,主要由三个机制:
dispatchers
: 如代码里面的cherrypy.dispatch
tools
: 如代码里面的cherrypy.tools
plugins
: 如代码里面的cherrypy.engine
对Web开发不熟悉,很多概念都是第一次接触,这个是对 CherryPy
最基础的一个手册文档的学习笔记,继续添加其它学习内容。
- http://www.cherrypy.org/
- https://github.com/cherrypy/cherrypy
- https://bitbucket.org/cherrypy/cherrypy/wiki/Home
- http://en.wikipedia.org/wiki/CherryPy
- http://tools.ietf.org/html/rfc7231
- http://en.wikipedia.org/wiki/Web_Server_Gateway_Interface
- http://legacy.python.org/dev/peps/pep-0333/
- https://cherrypy.readthedocs.org/en/latest/
- http://cherrypy.readthedocs.org/en/latest/tutorials.html
- https://www.digitalocean.com/community/tutorials/how-to-deploy-python-wsgi-applications-using-a-cherrypy-web-server-behind-nginx
- https://cherrypy.readthedocs.org/en/latest/tutorials.html#tutorials
- http://en.wikipedia.org/wiki/Session_(computer_science)
- http://www.ibm.com/developerworks/library/ws-restful/index.html
- http://zh.wikipedia.org/zh/REST
- http://jquery.com/