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

你可能感兴趣的:(python cherrypy RESTful API,cherrypy支持跨域ajax访问,CROS,crossdomain ajax)