AJAX 原理与 CORS 跨域

作者:赵帅强

原文:

https://segmentfault.com/a/1190000011549088

ajax作为前端开发必需的基础能力之一,你可能会使用它,但并不一定懂得其原理,以及更深入的服务器通信相关的知识。在最近两天的整理过程中,看了大量的文章,发现自己的后端能力已经限制自己在网络通信相关的知识领域的探索,还是应该尽快补齐短板。

下面我们来聊一聊 相关的东西,包括 的一部分内容,其中会抛弃一些被弃用的历史包袱,如IE6/7等。

Ajax的出现

2005年, 提出了Ajax的技术,其全称为 ,Ajax的核心是 对象,简称 ,它用于使浏览器向服务器请求额外的数据而不卸载页面,极大的提高了用户体验。在此之前,其实这种技术已经存在并被一些人实现,但并没有流行也没有被浏览器支持。不过在此之后,IE5第一次引入 对象,并支持 技术,后续被所有浏览器支持。

对象和请求

是一个API,为客户端提供服务端和客户端之间通信的功能,并且不会刷新页面。它并不仅仅能取回XML类型的数据,而能取回所有类型的数据,除了http协议,还支持file和ftp协议。我们可以通过其构造函数来创建一个新的 对象,这个操作需要在其它所有操作之前完成:

通过控制台我们可以很方便看到 的原型链: 。它拥有原型链上和本身的方法和属性,现在看下我们常用的方法:

我们解释下它的几个主要方法,我们在创建了新的xhr对象之后,首先要调用它的 方法:

在这里受同源策略的影响,当第二个参数url跨域的时候会被浏览器报安全错误。同源策略指的是当前页面和目标url协议、域名和端口均相同。后面也会讲到,除IE之外的浏览器通过XHR对象实现跨域请求,只需将url设置为绝对url即可。

当初始化请求完成后,我们调用 方法发送请求:

当请求的类型为 时,send()的参数会被忽略并置为null,send()传递的参数会影响到我们请求的头部 的默认值,该字段代表返回的资源内容的类型,用于浏览器处理,如果没有设置或在一些场景下,浏览器会进行MIME嗅探来确定怎么处理返回的资源。

在 中定义了 数据,用于常见的类表单数据序列化:

常用的方法有

,都是常用的跟数组类似的方法,不再解释。

请求方法

是最常见的请求类型,可以将查询字符串参数添加到URL尾部,对XHR而言,该查询字符串必须经过正确编码,每个键值对必须使用 进行编码,键值对之间由 分割:

请求使用频率仅次于 请求,通常发送较多数据,且格式不限,数据传递给 作为参数。

HTTP一共规定了九种请求方法,每一个动词代表不同的语义,但是常用的只有上面两种:

HTTP头部信息

每个HTTP请求和响应都带有头部信息,xhr对象允许我们操作部分头部信息。我们可以通过 方法来设置自定义的头部信息或者修改浏览器默认的正常头部信息。常用的请求头部:

我们一般不修改浏览器正常的头部信息,可能会影响到服务器响应。如果需要可以通过 进行修改:

设置头部信息需要在 之后, 之前进行调用。响应的头部信息在后端处理,不在此处讲解。有一部分请求头部信息不允许设置,如 等。

在请求返回后,我们可以获取到响应头部:

这里简单说下content-type值,指的是请求和响应的HTTP内容类型,影响到服务器和浏览器对数据的处理方式,默认为 ,常用的如:

对象的响应

我们现在对请求的发起很了解了,接着看下如何拿到响应数据。如果我们给 传递的第三个参数是 ,则代表为同步请求,那么js会被阻塞直到拿到响应,而如果为 则是异步请求,我们只需要绑定 事件监听响应即可。最上面的图已经说明了 的值含义,所以我们可以:

对象的响应数据中包含几个属性:

数据会出现在 中的哪一个,取决于服务器返回的 类型,当然我们也有一些方式在浏览器端设置如何处理这些数据:

响应数据相关的属性默认为 ,只有当请求完成并被正确解析的时候才会有值,取决于responseType的值,来确定 谁最终具有值。

的高级功能

在 里提供了超时和进度事件。

超时

在请求 之后开始计时,等待 时长后,如果没有收到响应,则触发 事件,超时会将 ,直接触发 事件。

请求进度

像上图所示, 定义了不同的进度事件: ,这其中我们已经说过了 事件为内容加载完成可用。现在说一下 进度事件:

该事件会接收一个 对象,其 属性为该xhr对象, 属性为 是否已知,即是否可用进度信息, 属性为已经接收的字节数, 为总字节数。该事件会在数据接收期间不断触发,但间隔不确定。

跨域

提到 对象,我们就会讲到跨域问题,它是为了预防某些恶意行为的安全策略,但有时候我们需要跨域来实现某些功能。需要注意的是跨域并不仅仅是前端单方面的事情,它需要后端代码进行配合,我们只是通过一些方式跳过了浏览器的阻拦。

对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。

的思想是浏览器和服务端通过头部信息来进行沟通确认是否给予响应。如:

如上就可以实现最简单的跨域访问,但是此时不能携带任何的 ,如果我们需要传递 进行身份认证,需要设置:

这样我们就可以传递认证信息了,但如果允许认证, 不能设置为 ,而一定是具体的域名信息。

现在的浏览器都对CORS有了实现,如IE使用 对象,其它浏览器使用 对象。所以在此之前有很多奇技淫巧,如通过 方法都不再详述,而且其都需要服务端配合并且有很多局限性。

IE实现:

XDR区别于XHR:

不能传输cookie

只能设置请求头部的content-type

不能访问响应头部信息

只支持get/post方法

通过这些区别可以阻止一部分的 和 。

XDR与XHR非常相似,区别有几点:

open()方法只接受两个参数,请求类型和URL

只允许异步请求

响应完成触发onload()事件,但我们只能访问responseText原始文本,并且无法获取响应的status.

异常事件都会触发error事件,并且无错误信息可用。

其余浏览器实现:

其余浏览器通过XHR对象直接实现了CORS,你只需要做的就是 方法中传入一个绝对URL。

相对于普通的XHR对象,CORS-XHR依然有部分限制:

不能使用setRequestHeader()定义头部

不能传递cookie

调用getAllResponseHeaders(),结果为空

其余跨域方法

上面的两种方法已经很成熟了,但是仍然有一部分方法可以跨域,比如 :

这种方式常用于服务端统计广告的点击次数,其缺陷为:

只能是GET请求

单向通信,无法获取响应文本

另外还有 :

这种方式通过和服务器配合,跨域请求一个js文件并被服务器处理后传回:

然后直接在浏览器调用了该函数,传回的数据被当做response形参进行处理。但它也有一些缺陷:

访问的方式是请求js,所以如果域名不安全,则很容易被恶意代码直接执行并攻击

无法检测是否错误,因为js不支持这样的接口事件,只能超时判断

上面两种方式很容易看出,我们在支持CORS之前,使用的方法只不过是采用 等不受跨域访问限制的对象,变相拿到了响应数据,但都有缺陷,所以如果没有历史包袱,建议采用XDR或XHR对象来实现跨域访问。

XHR的兼容性

我们可以直接到 这个网站上查询兼容性问题:

参考资料

MDN - 禁止修改的消息首部:

https://developer.mozilla.org/zh-CN/docs/Glossary/%E7%A6%81%E6%AD%A2%E4%BF%AE%E6%94%B9%E7%9A%84%E6%B6%88%E6%81%AF%E9%A6%96%E9%83%A8

理解HTTP之Content-Type:

http://homeway.me/2015/07/19/understand-http-about-content-type/

MDN - Content-Type:

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Content-Type

HTTP请求方法:

https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods

Javascript高级程序设计 第21章(Ajax与Comet)

你真的会使用XHR吗?

https://segmentfault.com/a/1190000004322487

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180824B1QZJC00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码关注腾讯云开发者

领取腾讯云代金券