Javascript 异步请求 ajax 实现跨域资源共享 CORS 的几种方法

跨域资源共享 2020-04-10 阅读 83 评论 0

问题描述

在当前域名 http://app.com 下的页面,使用 Jquery 的 .ajax() 异步访问另一个域名 http://test.com

$.ajax({
    data: {test: "test"},
    url: "http://test.com/index",
    dataType: "json",
    method: "post",
    success: function (data) {
        console.log(data);
    },
    error: function (error) {
        console.log(error);
    }
});

浏览器开发工具的控制台,会输出跨域的错误。

Access to XMLHttpRequest at 'http://test.com/index' from origin 'http://app.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
POST http://test.com/index net::ERR_FAILED

问题解决

一般有2种解决方案处理这个难题。

1. 服务端返回 Access-Control-Allow-Origin 头

就像控制台错误指出的一样:CORS(跨域资源共享)策略已阻止从原始站点 http://app.com 访问 http://test.com/index处的 XMLHttpRequest 请求:请求头没有出现 Access-Control-Allow-Origin 。浏览器一般是使用 同源策略(same-origin policy) 的机制来处理资源之间的交互,它能阻隔恶意文档,减少可能被攻击的媒介。

服务器如 Nginx、Apache 或者 IIS等,服务端语言如 PHP、Java、Node 等,都能返回 Access-Control-Allow-Origin 请求头,请求头的格式是 Access-Control-Allow-Origin: * (* 符号表示所有域名)或者 Access-Control-Allow-Origin:http://app.com(指定一个域名)。

2. 使用 jsonp

JSONP或JSON-P(JSON with Padding)是一种JavaScript技术,可通过加载 html 的 script 标签来请求数据。它是Bob Ippolito在2005年提出的,JSONP允许绕过同源策略共享数据,该策略不允许运行 JavaScript 代码读取从页面原始站点外部获取的媒体文档对象模型(DOM)元素或XMLHttpRequest数据。为了实现 jsonp,需要服务端和 Javascript 同时配合。

2.1 Javascript 实现 jsonp

jsonp 实际上是去构建一个 script 标签,指定 src,因此只能使用 get 请求,如果要传参数,可以将数据的键值对拼接在 url 上,必要时使用 encodeURIComponent 对参数编码。实现 jsonp 有以下几种方法。

2.1.1 使用原生js

script 标签的 src 属性,末尾加上时间参数,是为了防止出现缓存。

/**
 * jsonp 回调成功的方法
 * @param data
 */
function success(data) {
    console.log(data);
}
var script = document.createElement("script");
// 注意 callback 的参数值,需要与回调的方法名字一样
script.src = "http://test.com/index?callback=success&test=test&_=" + (new Date()).getTime();
// 脚本加载成功的回调
script.onload = function() {
    script.remove();    // 移除 script 标签
};
// 脚本加载失败的回调
script.onerror = function() {
    script.remove();    // 移除 script 标签
};
document.head.appendChild(script);

注意要添加 callback 参数,并且参数值必须与回调方法名一致。

2.1.2 使用 script 标签

2.1.1 的代码相当于加载一个 script 标签,与下面的语句是相等的。

<script type="text/javascript">
/**
 * jsonp 回调成功的方法
 * @param data
 */
function success(data) {
    console.log(data);
}
</script>
<script type="text/javascript" src="http://test.com/index?callback=success&test=test&_=13458766654879"></script>

2.1.3 使用 jquery 的 ajax

$.ajax({
    data: {test: "test"},
    url: "http://test.com/index",
    dataType: "jsonp",
    method: "get",
    success: function (data) {
        // 回调
        console.log(data);
    },
    error: function (error) {
        console.log(error);
    }
});

或者使用 jsonpCallback 指定一个回调的方法名,如下:

/**
 * jsonp 回调成功的方法
 * @param data
 */
function success(data) {
    console.log(data);
}
$.ajax({
    data: {test: "测试"},
    url: "http://test.com/index",
    method: "get",
    dataType: "jsonp",
    jsonpCallback: "success",
    error: function (error) {
        console.log(error);
    }
});

2.1.4 使用 jquery 的 getScript

/**
 * jsonp 回调成功的方法
 * @param data
 */
function success(data) {
    console.log(data);
}
var url = "http://test.com/index?callback=success&test=test&_=" + (new Date()).getTime();
$.getScript(url, function( data, textStatus, jqxhr ) {
    console.log( data ); // Data returned
    console.log( textStatus ); // Success
    console.log( jqxhr.status ); // 200
    console.log( "Load was performed." );
});

2.2 服务端返回 jsonp 数据

以上面的 JavaScript 请求为例子,服务端需要返回这样格式的数据:

var data = {"username":"test","password":"123456"};
success(data);

或者可以简单一点:

success({"username":"test","password":"123456"});

success 表示 url 中 callback 的参数值(因此,script 的 src 属性,不一定要包含 callback 参数名,可以是其他名字,需要与服务端协商好),是 JavaScript的回调方法名,传回给 JS 的数据必须包含在 () 括号里面,这里是一个 json,也可以是其他格式的数据,如

success('test')表示字符串,值为 test

success(123) 表示一个整型,值为 123

说到底,就是返回一个规范的 JavaScript,让浏览器去执行,只要把 jsonp 理解成是一个普通的 script 标签就行了,是不是很简单!

最后更新 2020-04-11
MIP.watch('startSearch', function (newVal, oldVal) { if(newVal) { var keyword = MIP.getData('keyword'); console.log(keyword); // 替换当前历史记录,新增 MIP.viewer.open('/s/' + keyword, {replace: true}); setTimeout(function () { MIP.setData({startSearch: false}) }, 1000); } }); MIP.watch('goHome', function (newVal, oldVal) { MIP.viewer.open('/', {replace: false}); });