cherrypy的文档说明比较少,看官网的例子,在python2.6中不能直接运行,需要修改如下:
#!/usr/bin/python2.6 ''' see also: http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm http://docs.cherrypy.org/stable/progguide/REST.html ''' import cherrypy class Resource(object): def __init__(self, content): self.content = content exposed = True def GET(self): return self.to_html() def PUT(self): self.content = self.from_html(cherrypy.request.body.read()) def to_html(self): html_item = lambda (name,value): '<div>%s:%s</div>'%(name, value) items = map(html_item, self.content.items()) items = ''.join(items) return '<html>{items}</html>'.format(**vars()) @staticmethod def from_html(data): pattern = re.compile(r'\<div\>(?P<name>.*?)\:(?P<value>.*?)\</div\>') items = [match.groups() for match in pattern.finditer(data)] return dict(items) class ResourceIndex(Resource): def to_html(self): html_item = lambda (name,value): '<div><a href="%s">%s</a></div>'%(value, name) items = map(html_item, self.content.items()) items = ''.join(items) return '<html>{items}</html>'.format(**vars()) class Root(object): pass root = Root() root.sidewinders = Resource({'color': 'red', 'weight': 176, 'type': 'stable'}) root.teebird = Resource({'color': 'green', 'weight': 173, 'type': 'overstable'}) root.blowfly = Resource({'color': 'purple', 'weight': 169, 'type': 'putter'}) # http://192.168.20.94:1970/resource_index root.resource_index = ResourceIndex({'sidewinder': 'sidewinder', 'teebird': 'teebird', 'blowfly': 'blowfly'}) conf = { 'global': { 'server.socket_host': '0.0.0.0', 'server.socket_port': 1970, }, '/': { 'request.dispatch': cherrypy.dispatch.MethodDispatcher(), } } cherrypy.quickstart(root, '/', conf)
#!/usr/bin/python2.6 import cherrypy import time; class Resource(object): exposed = True def __init__(self, content): self.content = content def GET(self): return self.content def PUT(self): self.content = cherrypy.request.body.read() class Empty(object): pass; class Root(object): exposed = True def __init__(self, links): self.links = links def GET(self): html_item = lambda (name, url): "<a href='%s'>%s</a><br/>"%(url, name); return "".join(map(html_item, self.links.items())); # http://192.168.20.94:1970/ root = Root({"the red color": "/red", "the green color":"/others/green"}) # http://192.168.20.94:1970/red root.red = Resource('red') # http://192.168.20.94:1970/others/green root.others = Empty(); root.others.green = Resource('green'); conf = { 'global': { 'server.socket_host': '0.0.0.0', 'server.socket_port': 1970, }, '/': { 'request.dispatch': cherrypy.dispatch.MethodDispatcher(), } } cherrypy.quickstart(root, '/', conf)
一个比较全的例子,和redmine的类似:
#!/usr/bin/python2.6 # -*- coding: utf-8 -*- ''' see also: http://tools.cherrypy.org/wiki/RestfulDispatch http://www.redmine.org/projects/redmine/wiki/Rest_api ''' import sys; import cherrypy import time; # set the default encoding to utf-8 # reload sys model to enable the getdefaultencoding method. reload(sys); # using exec to set the encoding, to avoid error in IDE. exec("sys.setdefaultencoding('utf-8')"); assert sys.getdefaultencoding().lower() == "utf-8"; class Stream(object): exposed = True; def __init__(self, stream_id): self.stream_id = stream_id; def GET(self): print("[Stream][GET] get a stream. id=%s"%(self.stream_id)); return self.stream_id def PUT(self): print("[Stream][PUT] update a stream. id=%s"%(self.stream_id)); self.stream_id = cherrypy.request.body.read() def DELETE(self): print("[Streams][DELETE] delete a stream. id=%s"%(self.stream_id)); def POST(self): print("[Streams][POST] create a stream. NotAllowed"); raise cherrypy.HTTPError(405) class Streams(object): exposed = True def __init__(self): pass; def GET(self): print("[Streams][GET] get all streams"); return "all stream list"; def PUT(self): print("[Streams][PUT] update all streams. NotAllowed"); raise cherrypy.HTTPError(405) def DELETE(self): print("[Streams][DELETE] delete all streams. NotAllowed"); raise cherrypy.HTTPError(405) def POST(self): print("[Streams][POST] create a new streams"); info = cherrypy.request.body.read() print("[Streams][POST] new stream created. info=%s"%(info)); def __getattr__(self, name): # stream operations. if name.isdigit(): return Stream(name); return object.__getattr__(name); class Root(object): exposed = True def __init__(self): pass; def GET(self): file = open("http.method.html"); data = file.read(); file.close(); return data; # http://192.168.20.94:1970/ root = Root() # http://192.168.20.94:1970/streams # http://192.168.20.94:1970/streams/100 root.streams = Streams(); conf = { 'global': { 'server.socket_host': '0.0.0.0', 'server.socket_port': 1970, 'tools.encode.on':True, 'tools.encode.encoding':'utf8', }, '/': { 'request.dispatch': cherrypy.dispatch.MethodDispatcher(), } } cherrypy.quickstart(root, '/', conf)
用作测试的http页面:
<!-- http.method.html --> <head> <title>SmartSystem1.0-HttpMethod</title> <meta http-equiv=Content-Type content="text/html;charset=utf-8"> </head> <style> div.item{ background-color: #00EEEE; width: 620px; margin-bottom: 3px; padding: 3 5 10 10; font-size: 12px; } div.title{ font-weight: bold; margin-bottom: 0px; } div.get_content{ background-color: #FFFFFF; width: 600px; height: 120px; padding: 3 5 3 5; } div.put_content{ } div.delete_content{ } div.post_content{ } font.warn{ color:#FF0000; background-color:#FFFF00; } </style> <body> <div> <div> <div class="item"> URL: <input type="text" id="url" size="50"></input><br/> <font class="warn">推荐用相对目录,因为AJAX不能跨域访问,</font> </div> </div> <div> <div class="item"> <div class="title"> HTTP GET: <input type="button" value="Do GET" onclick="do_get()"></input> </div> <div> <div class="get_content" id="get_content"></div> </div> </div> </div> <div> <div class="item"> <div class="title"> HTTP PUT: <input type="button" value="Do PUT" onclick="do_put()"></input> </div> <div> <textarea class="put_content" id="put_content" cols=84 rows=9></textarea> </div> </div> </div> <div> <div class="item"> <div class="title"> HTTP DELETE: <input type="button" value="Do DELETE" onclick="do_delete()"></input> </div> <div> <textarea class="delete_content" id="delete_content" cols=84 rows=9></textarea> </div> </div> </div> <div> <div class="item"> <div class="title"> HTTP POST: <input type="button" value="Do POST" onclick="do_post()"></input> </div> <div> <textarea class="post_content" id="post_content" cols=84 rows=9></textarea> </div> </div> </div> </div> <script type="text/javascript"> var url = document.getElementById("url"); var get_content = document.getElementById("get_content"); var put_content = document.getElementById("put_content"); var delete_content = document.getElementById("delete_content"); var post_content = document.getElementById("post_content"); // set default values. url.value = "streams/100"; put_content.value = "new_task"; delete_content.value = "100"; post_content.value = "color=red"; function do_get(){ get_content.innerText = ""; var ajax = new XMLHttpRequest(); ajax.open("GET", url.value, false); ajax.send(null); get_content.innerText = ajax.responseText; } function do_put(){ var ajax = new XMLHttpRequest(); ajax.open("PUT", url.value, false); //console.log("content:" + put_content.value); ajax.send(put_content.value); } function do_delete(){ var ajax = new XMLHttpRequest(); ajax.open("DELETE", url.value, false); ajax.send(delete_content.value); } function do_post(){ var ajax = new XMLHttpRequest(); ajax.open("POST", url.value, false); ajax.send(post_content.value); } </script> </body>
另外一个例子,可以支持将一个目录的所有静态文件(命名不能有句号)作为ui:
#!/usr/bin/python2.6 # -*- coding: utf-8 -*- ''' see also: http://tools.cherrypy.org/wiki/RestfulDispatch http://www.redmine.org/projects/redmine/wiki/Rest_api ''' import sys; import cherrypy import time; import re; import os; # set the default encoding to utf-8 # reload sys model to enable the getdefaultencoding method. reload(sys); # using exec to set the encoding, to avoid error in IDE. exec("sys.setdefaultencoding('utf-8')"); assert sys.getdefaultencoding().lower() == "utf-8"; class Stream(object): exposed = True; def __init__(self, stream_id): self.stream_id = stream_id; def GET(self): print("[Stream][GET] get a stream. id=%s"%(self.stream_id)); return "id=%s"%(self.stream_id); def PUT(self): print("[Stream][PUT] update a stream. id=%s"%(self.stream_id)); self.stream_id = cherrypy.request.body.read() def DELETE(self): print("[Streams][DELETE] delete a stream. id=%s"%(self.stream_id)); def POST(self): print("[Streams][POST] create a stream. NotAllowed"); raise cherrypy.HTTPError(405) class Streams(object): exposed = True def __init__(self): pass; def GET(self): print("[Streams][GET] get all streams"); return "all stream list"; def PUT(self): print("[Streams][PUT] update all streams. NotAllowed"); raise cherrypy.HTTPError(405) def DELETE(self): print("[Streams][DELETE] delete all streams. NotAllowed"); raise cherrypy.HTTPError(405) def POST(self): print("[Streams][POST] create a new streams"); info = cherrypy.request.body.read() print("[Streams][POST] new stream created. info=%s"%(info)); return "new stream created"; def __getattr__(self, name): # stream operations. if name.isdigit(): return Stream(name); return object.__getattr__(name); class StaticFile(object): exposed = True def __init__(self, filename): self.filename = filename; def GET(self): file = open(self.filename); data = file.read(); file.close(); return data; class UI(object): exposed = True def __init__(self): pass; def __getattr__(self, name): if os.path.exists(os.path.join("ui", name)): return StaticFile(os.path.join("ui", name)); return object.__getattr__(name); class Root(object): exposed = True def __init__(self): pass; def GET(self): raise cherrypy.HTTPRedirect("ui"); # http://192.168.20.94:1970/ root = Root() # http://192.168.20.94:1970/ui # http://192.168.20.94:1970/ui/method3 root.ui = UI() # http://192.168.20.94:1970/streams # http://192.168.20.94:1970/streams/100 root.streams = Streams(); conf = { 'global': { 'server.socket_host': '0.0.0.0', 'server.socket_port': 1970, 'tools.encode.on':True, 'tools.encode.encoding':'utf8', }, '/': { 'request.dispatch': cherrypy.dispatch.MethodDispatcher(), } } cherrypy.quickstart(root, '/', conf)
[winlin@dev6 cherrypy]$ tree ui ui ├── default └── method3
就会定向到ui目录:http://192.168.20.94:1970/ui/method3
其中method3的内容和上面的http.method.html是一样的。
ui/default是默认页面,可以用script跳转:
[winlin@dev6 cherrypy]$ cat ui/default <script>window.location.href="ui/method3"</script> [winlin@dev6 cherrypy]$
若需要开自己的线程,需要写cherrypy的插件来启动和停止:
http://docs.cherrypy.org/stable/progguide/extending/customplugins.html
http://stackoverflow.com/questions/2004514/force-cherrypy-child-threads
其中,cherrypy的main事件,是个loop,是另外一个线程启动的:
[cycle][Thread #139682221430528] tasks: {'2646': <__main__.Task instance at 0x7f0a30017d40>}
[Thread #139682016102160] delete task, id=2646
[Thread #139682026592016] delete task, id=2646
所以如果需要写同样的数据,是需要lock的。
js/ajax如何跨域,浏览器现在是有支持了的:
http://www.iteye.com/topic/600682
在没有CORS之前, 浏览器要发出跨域的请求时必须要得到被请求域的许可,而许可必须要以大家公认的方式,否则就被认为是不安全的。 那么CORS就定义了许可方式 它定义了 8个头信息来表示许可形式 分别是 * 4.1 Access-Control-Allow-Origin Response Header * 4.2 Access-Control-Max-Age Response Header * 4.3 Access-Control-Allow-Credentials Response Header * 4.4 Access-Control-Allow-Methods Response Header * 4.5 Access-Control-Allow-Headers Response Header * 4.6 Origin Request Header * 4.7 Access-Control-Request-Method Request Header * 4.8 Access-Control-Request-Headers Request Header 浏览器请求时, 必须带上Origin及Access-Control-Request-Method头信息(这些信息浏览器自动加的)。 分别表示来源网站及要使用的http方法, 当然这个请求一般是一个http Options方法。 例如: OPTIONS http://incubator.vicp.net/p/liuhan Host: incubator.vicp.net User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: zh-cn,zh;q=0.5 Accept-Encoding: gzip,deflate Accept-Charset: GB2312,utf-8;q=0.7,*;q=0.7 Keep-Alive: 300 Connection: keep-alive Origin: http://www.google.cn Access-Control-Request-Method: POST Access-Control-Request-Headers: x-requested-with 接下来关键来了, 服务器要在相应头里给予许可 即通过 Access-Control-Allow-Origin 头 表示允许的网站。 Access-Control-Allow-Methods 表示允许的方法 例如 响应 HTTP/1.1 200 OK Access-Control-Allow-Origin: http://arunranga.com Access-Control-Allow-Methods: POST, GET, OPTIONS Access-Control-Allow-Headers: X-PINGOTHER Access-Control-Max-Age: 1728000 浏览器拿到这些响应信息就可以发出正常的调用了。
死活试了很久,不知道如何用cherrypy支持跨域ajax访问。后来终于知道:
1. 除了要实现GET/POST/PUT/DELETE等,还需要实现OPTIONS。
2. 每个方法都需要设置response的头。
代码如下:
#!/usr/bin/python2.6 # -*- coding: utf-8 -*- ''' see also: http://tools.cherrypy.org/wiki/RestfulDispatch http://www.redmine.org/projects/redmine/wiki/Rest_api ''' import sys; import cherrypy import time; # set the default encoding to utf-8 # reload sys model to enable the getdefaultencoding method. reload(sys); # using exec to set the encoding, to avoid error in IDE. exec("sys.setdefaultencoding('utf-8')"); assert sys.getdefaultencoding().lower() == "utf-8"; # supprt crossdomain ajax script def enable_crossdomain(): cherrypy.response.headers["Access-Control-Allow-Origin"] = "*"; cherrypy.response.headers["Access-Control-Allow-Methods"] = "GET, POST, HEAD, PUT, DELETE"; #cherrypy.response.headers["Access-Control-Allow-Headers"] = "Cache-Control, X-Proxy-Authorization, X-Requested-With"; #cherrypy.response.headers["Access-Control-Max-Age"] = "604800"; class Stream(object): exposed = True; def __init__(self, stream_id): self.stream_id = stream_id; def GET(self): enable_crossdomain(); print("[Stream][GET] get a stream. id=%s"%(self.stream_id)); return "id=%s"%(self.stream_id); def PUT(self): enable_crossdomain(); print("[Stream][PUT] update a stream. id=%s"%(self.stream_id)); self.stream_id = cherrypy.request.body.read() def DELETE(self): enable_crossdomain(); print("[Streams][DELETE] delete a stream. id=%s"%(self.stream_id)); def POST(self): enable_crossdomain(); print("[Streams][POST] create a stream. NotAllowed"); raise cherrypy.HTTPError(405) def OPTIONS(self): enable_crossdomain(); class Streams(object): exposed = True def __init__(self): pass; def GET(self): enable_crossdomain(); print("[Streams][GET] get all streams"); return "all stream list"; def PUT(self): enable_crossdomain(); print("[Streams][PUT] update all streams. NotAllowed"); raise cherrypy.HTTPError(405) def DELETE(self): enable_crossdomain(); print("[Streams][DELETE] delete all streams. NotAllowed"); raise cherrypy.HTTPError(405) def POST(self): enable_crossdomain(); print("[Streams][POST] create a new streams"); info = cherrypy.request.body.read() print("[Streams][POST] new stream created. info=%s"%(info)); return "new stream created"; def __getattr__(self, name): # stream operations. if name.isdigit(): return Stream(name); return object.__getattr__(name); def OPTIONS(self): enable_crossdomain(); class Root(object): exposed = True def __init__(self): pass; def GET(self): file = open("http.method5.html"); data = file.read(); file.close(); return data; # http://192.168.20.94:1970/ root = Root() # http://192.168.20.94:1970/streams # http://192.168.20.94:1970/streams/100 root.streams = Streams(); conf = { 'global': { 'server.socket_host': '0.0.0.0', 'server.socket_port': 1970, 'tools.encode.on':True, 'tools.encode.encoding':'utf8', }, '/': { 'request.dispatch': cherrypy.dispatch.MethodDispatcher(), } } cherrypy.quickstart(root, '/', conf)客户端没有特殊需求,用ajax就可以。
<!-- http.method.html --> <head> <title>SmartSystem1.0-HttpMethod</title> <meta http-equiv=Content-Type content="text/html;charset=utf-8"> </head> <style> div.item{ background-color: #00EEEE; width: 620px; margin-bottom: 3px; padding: 3 5 10 10; font-size: 12px; } div.title{ font-weight: bold; margin-bottom: 0px; } div.get_content{ background-color: #FFFFFF; width: 600px; height: 120px; padding: 3 5 3 5; } div.put_content{ } div.delete_content{ } div.post_content{ } font.warn{ color:#FF0000; background-color:#FFFF00; } </style> <body> <div> <div> <div class="item"> URL: <input type="text" id="url" size="50"></input><br/> <font class="warn">支持AJAX跨域访问.</font> </div> </div> <div> <div class="item"> <div class="title"> HTTP GET: <input type="button" value="Do GET" onclick="do_get()"></input> </div> <div> <div class="get_content" id="get_content"></div> </div> </div> </div> <div> <div class="item"> <div class="title"> HTTP PUT: <input type="button" value="Do PUT" onclick="do_put()"></input> </div> <div> <textarea class="put_content" id="put_content" cols=84 rows=9></textarea> </div> </div> </div> <div> <div class="item"> <div class="title"> HTTP DELETE: <input type="button" value="Do DELETE" onclick="do_delete()"></input> </div> <div> <textarea class="delete_content" id="delete_content" cols=84 rows=9></textarea> </div> </div> </div> <div> <div class="item"> <div class="title"> HTTP POST: <input type="button" value="Do POST" onclick="do_post()"></input> </div> <div> <textarea class="post_content" id="post_content" cols=84 rows=9></textarea> </div> </div> </div> </div> <script type="text/javascript"> var url = document.getElementById("url"); var get_content = document.getElementById("get_content"); var put_content = document.getElementById("put_content"); var delete_content = document.getElementById("delete_content"); var post_content = document.getElementById("post_content"); // set default values. url.value = "http://192.168.20.94:1970/streams"; put_content.value = "new_task"; delete_content.value = "100"; post_content.value = "color=red"; function do_get(){ get_content.innerText = ""; var ajax = new XMLHttpRequest(); ajax.open("GET", url.value, false); ret = ajax.send(null); get_content.innerText = ajax.responseText; } function do_put(){ var ajax = new XMLHttpRequest(); ajax.open("PUT", url.value, false); //console.log("content:" + put_content.value); ajax.send(put_content.value); } function do_delete(){ var ajax = new XMLHttpRequest(); ajax.open("DELETE", url.value, false); ajax.send(delete_content.value); } function do_post(){ var ajax = new XMLHttpRequest(); ajax.open("POST", url.value, false); ajax.send(post_content.value); } </script> </body>
响应头的charset,如果在配置中设置了:'tools.encode.encoding': 'utf-8',则永远是utf-8。
如果这个设置错误,cherrypy.response.headers是没有办法改过来的。
如果服务器返回的header的Content-Type的charset是错误的,ie的ajax会报错。
另外,ie的脚本解析也和chrome不太一样。
ie的js的语法更严格,局部变量要用var,最后一个参数后面不能有逗号等。