随着软件开发过程分工越来越细,前段开发和后端开发、移动端开发越来越分离,后端设计的RESTFul API需要被其他的应用访问,因浏览器的安全限制,在跨站点进行访问的时候会有安全限制;CORS解决不同的站点之间的资源共享问题,关于CORS可以参看维基百科
实验环境
站点1是一个HTML页面,可以理解为RESTFul API的消费端
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<h1>for test</h1>
<script src="{{static_url('jquery-1.6.2.min.js')}}"></script>
<script type="text/javascript">
$(function () {
//alert('aaaaaaa');
$.ajax(
{
url: "http://localhost:8082/api/test_cors",
success: function (res) {
alert("bbbbbb");
},
});
$.ajax(
{
url: "http://localhost:8082/api/test_cors",
//data:JSON.stringify({a:"123",b:"345"}),
//contentType:"application/json",
data:{a:"123",b:"345"},
type:'POST',
dataType: "json",
headers:{"x-token":"aaaaaaaaaasdfjsd;fajasd;fjsd;jfad;sfjasd;fjasd"},
success: function (res) {
alert("cccc");
},
});
});
</script>
</body>
</html>
页面运行在http://localhost:8888
下,功能是从另外一个站点http://localhost:8082/api/test_cors
上进行GET
和POST
请求,进行POST
请求分为两种情况
- content-type=='application/json'
- content-type=='application/x-www-form-urlencoded' #默认情况下,ajax都采用这种方式进行POST提交
站点2可以理解为一个 RESTFul API的提供者,提供的服务供其他的站点进行访问
代码如下:
# -*- coding:utf-8 -*-
from tornado.gen import coroutine,IOLoop
from tornado.options import parse_command_line,options
from tornado.web import RequestHandler,Application ,escape
from tornado.log import gen_log
def main():
parse_command_line()
app=Application(handlers=[
(r'/api/test_cors',TestService)
],debug=True)
app.listen('8082')
IOLoop.current().start()
class CORSHandler(RequestHandler):
"""
跨站点请求的处理(Handler)器
"""
def options(self):
gen_log.info('entry options method')
#self._add_cors_request_headers()
def _add_cors_request_headers(self):
"""
添加跨站点请求的头信息
"""
headers=self.request.headers
origin=headers.get("Origin",None)
allow_method=headers.get('Access-Control-Request-Method','')
custome_header=headers.get('Access-Control-Request-Headers','')
# output all follow to client
self.add_header('Access-Control-Allow-Origin',origin or '*')
self.add_header('Access-Control-Allow-Methods',','.join(self.SUPPORTED_METHODS))
self.add_header('Access-Control-Allow-Headers',custome_header)
self.add_header('Access-Control-Allow-Credentials',True)
self.add_header('Access-Control-Max-Age',1728000)
def get_current_user(self):
"""
获取当前用户
:return:
"""
return {
"empCd":"12860",
"pwd":"nothing"
}
@property
def user(self):
return self.current_user
def write_json(self,data,code=0,err_msg=None):
""""""
#self._add_cors_request_headers()
self.set_header("content-type","application/json")
self.write({"data":data,"code":code,"err_msg":err_msg})
class TestService(CORSHandler):
@coroutine
def get(self):
data = {
"a": "a",
"b": "b"
}
self.write_json(data)
@coroutine
def post(self):
# todo if content-type is application/json
#json_body=escape.json_decode(self.request.body)
#a = json_body.get("a")
#b = json_body.get("b")
# todo if content-type is application/x-www-form-urlencoded
a=self.get_argument('a')
b=self.get_argument('b')
self.write_json({"a": a, "b": b})
if __name__ =='__main__':
main()
实验分为两部分
- 简单请求的跨站点访问验证
这里的简单请求时指,请求的content-type是application/x-www-form-urlencoded和multipart/form-data
从代码来看浏览器应该会发送两个网路请求给服务器,如果服务器正确的响应了浏览器的请求则浏览器会弹出提示框, 上述代码运行效果如下:
实际上没有接收到浏览器的提示框,在控制台看到跨域的警告消息。为了解决跨域的警告,开启cors的支持。 将代码的self._add_cors_reuest_headers的注释取消掉
def write_json(self,data,code=0,err_msg=None):
""""""
self._add_cors_reuest_headers() #开启cors的支持
开启cors的支持后,运行效果: 警告消失了 发出去的请求数是2个
- 非简单请求的跨站点访问验证
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。随着前后端开发的分离合前端开发的很多js框架发起的网路请求默认content-type都是application/json不是application/x-www-form-urlencoded.
修改html中的代码,使ajax进行post提交的时候,发送的content-type是application/json且表单的数据修改为json的格式,修改内容如下:
修改前:
url: "http://localhost:8082/api/test_cors",
//data:JSON.stringify({a:"123",b:"345"}),
//contentType:"application/json",
data:{a:"123",b:"345"},
省略...
修改后:
url: "http://localhost:8082/api/test_cors",
data:JSON.stringify({a:"123",b:"345"}),
contentType:"application/json",
//data:{a:"123",b:"345"},
省略...
刷新页面,运行效果如下: 提示有错误
从网络请求中可以看到,多了一个OPTIONS的请求,但是没有POST的请求。按照CORS的规范,非普通请求会预先发起一个OPTIONS请求给跨站点的资源,如果OPTIONS响应的内容允许访问则再次发起POST请求。出现OPTIONS请求正常但是POST请求没有发送的情况,可能的问题出来OPTIONS方法的代码。修改OPTIONS的代码:
修改前:
def options(self):
gen_log.info('entry options method')
#self._add_cors_request_headers()
@coroutine
def post(self):
# todo if content-type is application/json
#json_body=escape.json_decode(self.request.body)
#a = json_body.get("a")
#b = json_body.get("b")
# todo if content-type is application/x-www-form-urlencoded
a=self.get_argument('a')
b=self.get_argument('b')
修改后:
def options(self):
gen_log.info('entry options method')
self._add_cors_request_headers()
@coroutine
def post(self):
# todo if content-type is application/json
json_body=escape.json_decode(self.request.body)
a = json_body.get("a")
b = json_body.get("b")
# todo if content-type is application/x-www-form-urlencoded
#a=self.get_argument('a')
#b=self.get_argument('b')
刷新页面查看运行效果:
正常的访问了其他站点上的资源,以前的过程一直有一个核心的方法在起作用,可以看看:
def _add_cors_request_headers(self):
"""
添加跨站点请求的头信息
"""
headers=self.request.headers
origin=headers.get("Origin",None)
allow_method=headers.get('Access-Control-Request-Method','')
custome_header=headers.get('Access-Control-Request-Headers','')
# output all follow to client
self.add_header('Access-Control-Allow-Origin',origin or '*')
self.add_header('Access-Control-Allow-Methods',','.join(self.SUPPORTED_METHODS))
self.add_header('Access-Control-Allow-Headers',custome_header)
self.add_header('Access-Control-Allow-Credentials',True)
self.add_header('Access-Control-Max-Age',1728000)
详细的解释参看阮一峰的网络日志。
小结:
CORS很好的解决了前后端分离开发时的跨站点请求问题,让前后端协同更加顺利。当前后端分离开发后也可以采用类似NGINX反向代理的方案来解决API的调用问题,但CORS配置更少,更透明。遗憾的是目前在IE浏览器上IE9以下都不支持CORS。
参考链接: