Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Blog] 常见跨域解决方案 #30

Open
lvqq opened this issue May 27, 2020 · 0 comments
Open

[Blog] 常见跨域解决方案 #30

lvqq opened this issue May 27, 2020 · 0 comments
Labels

Comments

@lvqq
Copy link
Owner

lvqq commented May 27, 2020

不论是开放的API接口,还是部署在不同服务器的项目,很多都绕不开跨域这个问题,那么跨域有哪些常见的解决方案呢?

jsonp

jsonp主要依赖script标签的src属性可以实现跨域访问,在请求的url后拼上相应的回调函数字段,后端也需要对返回的数据外包一层函数名进行处理。

如何使用

jsonp由两部分组成:回调函数和传入的数据,很重要的一点:jsonp 只支持
GET 方法
,不支持POST

这里以豆瓣的API为例,实现一个跨域请求。url为:https://api.douban.com/v2/book/search?q=JavaScript高级程序设计&count=2

url中q表示查询图书时输入的信息,count表示查询结果的条目数。这里以查询JavaScript高级程序设计为例,结果为2条。

<script type="text/javascript">
  //定义自己的回调函数
  function handleResponse(data){
    console.log(data);
  }
</script>

<!-- 将自己的回调函数拼在url后的callback中 -->
<script type="text/javascript" src="https://api.douban.com/v2/book/search?q=JavaScript高级程序设计&count=2&callback=handleResponse"></script>

通过这样的方法,将传入数据拼在url后,将自己的回调函数拼在url的callback中,再在回调函数中对获取到的数据进行处理,就实现了发起跨域请求。

动态获取

这里设置一个button,点击后动态获取数据,代码如下:

<script type="text/javascript">
    function handleResponse(data){
        console.log(data.books[1]);
    }
</script>

<script type="text/javascript">
	window.onload = function() {
        var btn = document.getElementById('btn');
        btn.onclick = function() {
            var script = document.createElement('script');
            script.src = 'https://api.douban.com/v2/book/search?q=JavaScript高级程序设计&count=2&callback=handleResponse';
            document.body.appendChild(script);
        }
    }
</script>

点击后可以看到成功获取到了数据:

jQuery中使用 jsonp

$.ajax({
    type: "get",
    url: "https://api.douban.com/v2/book/search?q=JavaScript高级程序设计&count=2",
    dataType: "jsonp",  // 将返回的数据类型设置为jsonp方式
    jsonp: "callback",   //请求php的参数名
    jsonpCallback: "handleResponse",  //要执行的回调函数
    success: function(data) {
        console.log(data);
    }
});

这里会先调用指定的 handleResponse ,然后再调用 success。其中 handleResponse 是随着参数传入的回调函数,success是该请求成功发送成功时一定会调用的回调函数,怎么使用就看你怎么写了。

使用$.getJSON()调用如下:

$.getJSON("https://api.douban.com/v2/book/search?q=JavaScript高级程序设计&count=2&callback=?", function(data){
	console.log(data);
});

将url作为第一个参数传入,其中令callback=?,将回调函数作为第二个参数传入,这样也可实现跨域,会得到和前面一样的请求结果。

jsonp 的局限性

  • 只支持get方法
  • 请求是否失败难以判断
  • 请求域的安全性问题

CORS

CORS全称是“跨域资源共享”,允许向跨域服务器发送Ajax请求。对于开发者来说,和使用Ajax没有什么区别,关键在于服务器,只要服务器实现了CORS的支持,就能实现跨域访问。关于CORS的兼容性如下:

可以看到绝大多数浏览器都支持CORS,而IE则必须在IE10及以上。IE10以下的则使用的是XDomainRequest对象,这里就不展开了。

CORS请求可以分为简单请求非简单请求两种,浏览器对这两种请求的处理方式有所不同。

简单请求

若请求满足以下所有条件,则该请求视为“简单请求”:

  • 使用以下方法之一:

    • GET
    • POST
    • HEAD
  • HTTP首部不得设置以下集合之外的字段:

    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type
    • DPR
    • Downlink
    • Save-Data
    • Viewport-Width
    • Width
  • Content-Type 的值仅限于下列三者之一:

    • text/plain
    • multipart/form-data
    • application/x-www-form-urlencoded

对于简单请求,浏览器会直接发送CORS请求,在请求报文的头部信息中自动添加一个Origin字段,表示发起请求的源。例如:

Origin: http://foo.example

而服务端返回的响应首部中则有Access-Control-Allow-Origin字段:

Access-Control-Allow-Origin: http://foo.example

表示允许域名为http://foo.example的外域向自己发起跨域的CORS请求,如果想让任意域名都能发起请求,可以将它的值设置为*

非简单请求

如果是非简单请求,那么在发起CORS请求前,必须使用OPTIONS方法发起一个预检请求。
该预检请求中也会自动添加一个Origin字段表示请求源,还会携带以下两个字段:

Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER

分别表示此次请求使用的方法和额外的自定义请求首部(若有多个则用逗号隔开)。
如果服务器通过了预检请求,那么返回的响应首部中会有如下几个字段:

Access-Control-Allow-Origin: http://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
  • Access-Control-Allow-Origin与简单请求中的一致。
  • Access-Control-Allow-Methods表示服务端允许客户端发起请求时使用的所有方法。
  • Access-Control-Allow-Headers表示客户端发起请求时允许携带的请求首部。
  • Access-Control-Max-Age则表示预检请求过期的时间,单位为秒,这里是86400秒,也就是24小时。

关于CORS更为详细的介绍可以参考:

iframe

在存在iframe的页面中,要想发起跨域访问,可以采用降域或者postMessage的方式实现。

iframe 降域

降域的前提是二者的主域名要一致,例如a.example.comb.example.com。在当前页面和iframe源页面均需要设置document.domain属性才能实现降域,这样二者可以跨域访问:

document.domain = "example.com";

postMessage

包含iframe的页面中还可以使用postMessage进行消息传递来进行跨域访问。

<!-- index.html 父页面 -->

<h1>this is index</h1>

<iframe src="./iframe.html" id="myiframe"></iframe>
<!-- iframe.html 子页面 -->

<h1>this is iframe</h1>

这里有两个不同源的页面index.html和其中包含的iframe的源页面iframe.html

父页面向子页面发送消息

//index.js

var myiframe = document.getElementById('myiframe');

myiframe.onload = function () {
  myiframe.contentWindow.postMessage('data from index', '*');
}

首先获取iframe元素,然后当它加载完成后向它发送一条消息,这里的contentWindow表示获取的iframe页面的window对象,postMessage方法挂载在window对象上。

postMessage方法接受的第一个参数是发送的数据,可以是任何原始类型的数据。第二个参数表示发送到的url,这里设置为*表示所有url都允许。还有更高级的第三个可选参数,这里就不展开了。

//iframe.js

window.onmessage = function (event) {
  console.log(event.data);
}

然后在iframe页面中监听message事件即可,event.data即为发送的数据。

子页面向父页面发送消息

//iframe.js

parent.postMessage('data from iframe', '*')

iframe的源页面中,直接使用parent关键字即可获得父页面的window对象,然后调用postMessage发送数据。

//index.js

window.onmessage = function (event) {
  console.log(event.data);
}

同样的,在父页面中监听message事件来捕获子页面的消息传递。

参考:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant