python cherrypy RESTful API,cherrypy支持跨域ajax访问,CROS,crossdomain ajax


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)

ui的目录结构如下:

[winlin@dev6 cherrypy]$ tree ui
ui
├── default
└── method3

这样访问:http://192.168.20.94:1970

就会定向到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,最后一个参数后面不能有逗号等。

智能推荐

注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
© 2014-2019 ITdaan.com 粤ICP备14056181号  

赞助商广告