一、为什么会出现跨域
跨域问题来源于JavaScript的同源策略,即只有 协议+主机名+端口号 (如存在)相同,则允许相互访问。即JavaScript只能访问和操作自己域下的资源,不能访问和操作其他域下的资源。
跨域问题是针对JS和ajax的,html本身没有跨域问题,比如a标签、script标签、甚至form标签(可以直接跨域发送数据并接收数据)
注意:localhost和127.0.0.1也属于跨域。
1. 跨域产生的原理
- 当向不同源的资源发起Ajax请求时, 浏览器会加上Origin字段来标识源
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
Content-Length: 8
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Host: localhost:7070
Origin: http://localhost:9090 协议+域名+端口
- 服务器会根据Origin字段决定是否同意这次请求
如果Origin指定的源不在许可范围内, 服务器会返回一个不带有Access-Control-Allow-Origin字段的响应. 浏览器解析时发现缺少了这个字段, 就会报错.

注意:这种错误不能通过状态码识别, 因为状态码很有可能就是200.
解决方案一、在服务端添加响应头Access-Control-Allow-Origin
前面说明了,Ajax跨域失败是因为响应中缺少了响应头Access-Control-Allow-Origin, 那么就想办法加上去.
修改Django中的views.py文件修改views.py中对应API的实现函数,给返回值加上响应头Access-Control-Allow-Origin,允许其他域通过Ajax请求数据:
区分简单的请求和复杂的请求
简单请求:
满足以下两个条件的请求。
(1) 请求方法是以下三种方法之一:
- HEAD
- GET
- POST
(2) HTTP的头信息不超出以下几种字段:
- Accept
- Accept-Language
- Content-Language
- Last-Event-ID
- Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
复杂请求:
非简单请求就是复杂请求。
非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。
非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。
预检请求为OPTIONS请求,用于向服务器请求权限信息的。
预检请求被成功响应后,才会发出真实请求,携带真实数据。
a. 简单的请求只需要加上"Access-Control-Allow-Origin"
def myview(_request):
response = HttpResponse(json.dumps({"key": "value", "key2": "value"}))
response["Access-Control-Allow-Origin"] = "*" #也可换成想要访问的域名
response["Access-Control-Allow-Methods"] = "POST, GET, OPTIONS"
response["Access-Control-Max-Age"] = "1000"
response["Access-Control-Allow-Headers"] = "*"
return response
b. 复杂的请求需要设置两步
- 服务器端后台接口允许OPTIONS请求,如果是非简单的请求方法,需要设置请求访问的方式
- 后端增加对应的头部支持
# DELETE方法下的跨域
def myview(request):
if request.method == 'OPTION':
res = HttpResponse()
response["Access-Control-Allow-Methods"] = " DELETE"
response["Access-Control-Allow-Headers"] = "*"
return res
res = HttpResponse()
response["Access-Control-Allow-Headers"] = "*"
return res
解决方法二、利用JSONP
JSONP是JSON with Padding的略称。它是一个非官方的协议,它允许在服务器端集成Script tags返回至客户端,通过javascript callback的形式实现跨域访问(这仅仅是JSONP简单的实现形式)。

1. JSONP实现流程
JSONP的实现步骤大致如下(参考了来源中的文章)
- i. 客户端网页网页通过添加一个<script>元素,向服务器请求JSON数据,这种做法不受同源政策限制
function addScriptTag(src) {
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = src;
document.body.appendChild(script);
}
window.onload = function () {
addScriptTag('http://example.com/ip?callback=foo');
}
function foo(data) {
console.log('response data: ' + JSON.stringify(data));
};
请求时,接口地址是作为构建出的脚本标签的src的,这样,当脚本标签构建出来时,最终的src是接口返回的内容
- ii. 服务端对应的接口在返回参数外面添加函数包裹层
foo({
"test": "testData"
});
- iii. 由于<script>元素请求的脚本,直接作为代码运行。
这时,只要浏览器定义了foo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象,而不是字符串,因此避免了使用JSON.parse的步骤。
注意: 一般的JSONP接口和普通接口返回数据是有区别的,所以接口如果要做JSONP兼容,需要进行判断是否有对应callback关键字参数,如果有则是JSONP请求,返回JSONP数据,否则返回普通数据
JSONP使用注意
基于JSONP的实现原理,所以JSONP只能是“GET”请求,不能进行较为复杂的POST和其它请求,所以遇到那种情况,就得参考下面的CORS解决跨域了(所以如今它也基本被淘汰了)。
2. 基于JS的JSONP的实现
前面讲了JSONP的实现原理,现在我们可以自己写JS来实现JSONP功能。
一般情况下,我们希望这个script标签能够动态的调用,而不是像固定在html里面所以没等页面显示就执行了,很不灵活。
我们可以通过页面的触发事件操作后,通过javascript动态的创建script标签,这样我们就可以灵活调用远程服务。实例如下:
#项目1中html部分代码
<script>
//回调函数
function bar(data) {
alert(data);
console.log(JSON.parse(data))
}
</script>
<script>
//触发事件生成script标签
$(".ajax_btn").click(function () {
script_request("http://127.0.0.1:8002/send_ajax/?callback=bar") //请求路径带回调函数名
});
//定义一个生成script标签的函数
function script_request(url) {
var $script=$("<script>");
$script.attr("src",url);
$("body").append($script);
$("body script:last").remove()
}
</script>
#项目2中的视图函数,即上述script标签请求的路径
def send_ajax(request):
import json
func_name=request.GET.get("callback") #获得回调函数的名字
dic={"k1":"v1"}
return HttpResponse("%s('%s')" %(func_name,json.dumps(dic)))
为了更加灵活,上述我们将你自己在客户端定义的回调函数的函数名传送给服务端,服务端则会返回以你定义的回调函数名的方法,将获取的json数据传入这个方法完成回调。
3. 基于jquery的JSONP的实现: $.getJSON()
/项目1中html中部分代码
<script>
$(".ajax_btn").click(function () {
$.getJSON("http://127.0.0.1:8002/send_ajax/?callback=?",function (data) {
alert(data);
console.log(data)
})
});
</script>
//项目2中的视图函数,即跨域请求的路径
def send_ajax(request):
import json
func_name=request.GET.get("callback") #获得回调函数的名字
dic={"k1":"v1"} print("ok")
return HttpResponse("%s('%s')" %(func_name,json.dumps(dic)))
如上,jQuery框架也当然支持JSONP,可以使用 $.getJSON(url,[data],[callback])
方法。
与js实现的方式相比,我们并不要自己生成一个script标签,客户端也并不需要自己定义一个回调函数.
注意: 在$.getJSON(url,[data],[callback])方法的url的后面必须添加一个callback参数,这样getJSON方法才会知道是用JSONP方式去访问服务,callback后面的那个问号是内部自动生成的一个回调函数名。
4. 基于jquery的JSONP的实现:dataType: JSONP
上述这种方法,很方便,不需要我们自己定义回调函数和指定回调函数名,但是,如果说我们想指定自己的回调函数名,或者说服务上规定了固定回调函数名该怎么办呢?
我们可以使用$.ajax方法来实现。如下例:
//项目1中html中部分代码
<script>
$(".ajax_btn").click(function () {
$.ajax({
url:"http://127.0.0.1:8002/send_ajax/",
dataType:"jsonp", //相当于script标签
jsonp:'callbacks', //相当于路径中回调函数路径参数键值对的键
jsonpCallback:"func" //相当于路径中回调函数路径参数键值对的值,回调函数名
})
});
//定义回调函数
function func(arg) {
alert(arg);
console.log(arg)
}
</script>
//项目2中的视图函数,即跨域请求的路径
def send_ajax(request):
import json
func_name=request.GET.get("callbacks") #获得回调函数的名字
dic={"k1":"v1"}
print("ok")
return HttpResponse("%s('%s')" %(func_name,json.dumps(dic)))
5. 基于jquery的JSONP的实现(三)
在上小节中jsonp: 'callbacks'就是定义一个存放回调函数的键,jsonpCallback是前端定义好的回调函数方法名,server端接受callback键对应值后就可以在其中填充数据打包返回。
但是,jsonpCallback参数可以不定义,jquery会自动定义一个随机名发过去,那前端就得用回调函数来处理对应数据了。利用jQuery可以很方便的实现JSONP来进行跨域访问。
//项目1中html中部分代码
<script>
$(".ajax_btn").click(function () {
$.ajax({
url:"http://127.0.0.1:8002/send_ajax/",
dataType:"jsonp",
jsonp:'callbacks',
success:function (data) {
alert(data);
console.log(data)
}
})
});
</script>
//项目2中的视图函数,即跨域请求的路径
def send_ajax(request):
import json
func_name=request.GET.get("callbacks") #获得回调函数的名字
dic={"k1":"v1"}
print("ok")
return HttpResponse("%s('%s')" %(func_name,json.dumps(dic)))
解决办法三、使用Django提供的 Django-cors-headers 来处理跨域
-
从GitHub上面下载Django-cors-headers
-
pip install Django-cors-headers.zip
-
在settings.py中的中间件中配置 【'corsheaders.middleware.CorsMiddleware',】
注意: 这个中间件一定要写在CSRF之前,为了方便处理,一般写在最前面.
- 设置 CORS_ORIGIN_ALLOW_ALL = True,即允许所有的跨域请求,当然,这里也可以设置为False,然后配合 CORS_ORIGIN_WHITELIST 白名单来使用
INSTALLED_APPS = [
...
'corsheaders',
...
]
MIDDLEWARE_CLASSES = (
...
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware', # 注意顺序
...
)
#跨域增加忽略
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_WHITELIST = (
'*'
)
CORS_ALLOW_METHODS = (
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
'VIEW',
)
CORS_ALLOW_HEADERS = (
'XMLHttpRequest',
'X_FILENAME',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
'Pragma',
)
如此,我们的跨域处理即完成,支持所有的请求。
网友评论