来源:segmentfault.com
ajax作为前端开发必需的基础能力之一,你可能会使用它,但并不一定懂得其原理,以及更深入的服务器通信相关的知识。在最近两天的整理过程中,看了大量的文章,发现自己的后端能力已经限制自己在网络通信相关的知识领域的探索,还是应该尽快补齐短板。
下面我们来聊一聊ajax相关的东西,包括xhr/xdr/ajax/cors/http
的一部分内容,其中会抛弃一些被弃用的历史包袱,如IE6/7等。
Ajax的出现
2005年,Jesse James Garrett提出了Ajax的技术,其全称为Asynchronous Javascript and XML
,Ajax
的核心是XMLHttpRequest
对象,简称XHR,它用于使浏览器向服务器请求额外的数据而不卸载页面,极大的提高了用户体验。在此之前,其实这种技术已经存在并被一些人实现,但并没有流行也没有被浏览器支持。不过在此之后,IE5第一次引入XHR对象,并支持ajax技术,后续被所有浏览器支持。
XMLHttpRequest对象和请求
XHR是一个API,为客户端提供服务端和客户端之间通信的功能,并且不会刷新页面。它并不仅仅能取回XML类型的数据,而能取回所有类型的数据,除了http协议,还支持file和ftp协议。我们可以通过其构造函数来创建一个新的XHR对象,这个操作需要在其它所有操作之前完成:
var xhr = new XMLHttpRequest();
通过控制台我们可以很方便看到XHR的原型链:Object -> EventTarget -> XMLHttpRequestEventTarget -> XMLHttpRequest
。它拥有原型链上和本身的方法和属性,现在看下我们常用的方法:
我们解释下它的几个主要方法,我们在创建了新的xhr对象之后,首先要调用它的open()
方法:
|
|
在这里受同源策略的影响,当第二个参数url跨域的时候会被浏览器报安全错误。同源策略指的是当前页面和目标url协议、域名和端口均相同。后面也会讲到,除IE之外的浏览器通过XHR对象实现跨域请求,只需将url设置为绝对url即可。
当初始化请求完成后,我们调用send()
方法发送请求:
|
|
当请求的类型为get/head
时,send()
的参数会被忽略并置为null
,send()
传递的参数会影响到我们请求的头部content-type
的默认值,该字段代表返回的资源内容的类型,用于浏览器处理,如果没有设置或在一些场景下,浏览器会进行MIME嗅探来确定怎么处理返回的资源。
在XHR2级中定义了FormData数据,用于常见的类表单数据序列化:
FormData常用的方法有append/delete/entries/forEach/get/getAll/has/keys/set/values
,都是常用的跟数组类似的方法,不再解释。
请求方法
GET是最常见的请求类型,可以将查询字符串参数添加到URL尾部,对XHR而言,该查询字符串必须经过正确编码,每个键值对必须使用encodeURIComponent()
进行编码,键值对之间由&分割:
POST请求使用频率仅次于GET请求,通常发送较多数据,且格式不限,数据传递给send()
作为参数。
HTTP一共规定了九种请求方法,每一个动词代表不同的语义,但是常用的只有上面两种:
- OPTIONS:返回服务器针对特定资源所支持的HTTP请求方法。也可以利用向Web服务器发送’*’的请求来测试服务器的功能性。
- HEAD:向服务器索要与GET请求相一致的响应,只不过响应体将不会被返回。这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应消息头中的元信息。
- GET:向特定的资源发出请求。
- POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的创建和/或已有资源的修改。
- PUT:向指定资源位置上传其最新内容。
- DELETE:请求服务器删除Request-URI所标识的资源。
- TRACE:回显服务器收到的请求,主要用于测试或诊断。
- CONNECT:HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
- PATCH: 用于对资源进行部分修改
HTTP头部信息
每个HTTP请求和响应都带有头部信息,xhr对象允许我们操作部分头部信息。我们可以通过xhr.setRequestHeader()
方法来设置自定义的头部信息或者修改浏览器默认的正常头部信息。常用的请求头部:
我们一般不修改浏览器正常的头部信息,可能会影响到服务器响应。如果需要可以通过xhr.setRequestHeader()
进行修改:
设置头部信息需要在open()
之后,send()
之前进行调用。响应的头部信息在后端处理,不在此处讲解。有一部分请求头部信息不允许设置,如Accept-Encoding, Cookie
等。
在请求返回后,我们可以获取到响应头部:
这里简单说下content-type
值,指的是请求和响应的HTTP内容类型,影响到服务器和浏览器对数据的处理方式,默认为text/html
,常用的如:
XHR对象的响应
我们现在对请求的发起很了解了,接着看下如何拿到响应数据。如果我们给open()
传递的第三个参数是true,则代表为同步请求,那么js会被阻塞直到拿到响应,而如果为false则是异步请求,我们只需要绑定xhr.onreadystatechange()
事件监听响应即可。最上面的图已经说明了readystate
的值含义,所以我们可以:
xhr对象的响应数据中包含几个属性:
数据会出现在responseText/responseXML
中的哪一个,取决于服务器返回的MIME类型,当然我们也有一些方式在浏览器端设置如何处理这些数据:
响应数据相关的属性默认为null / ''
,只有当请求完成并被正确解析的时候才会有值,取决于responseType
的值,来确定response/responseText/responseXML
谁最终具有值。
XHR的高级功能
在xhr v2里提供了超时和进度事件。
超时
|
|
在请求send(
)之后开始计时,等待timeout
时长后,如果没有收到响应,则触发ontimeout()
事件,超时会将readystate=4
,直接触发onreadystatechange()
事件。
请求进度
像上图所示,xhr v2定义了不同的进度事件:loadstart/progress/error/abort/load/loadend
,这其中我们已经说过了onload()
事件为内容加载完成可用。现在说一下`onprogress()``进度事件:
该事件会接收一个event
对象,其target
属性为该xhr对象,lengthComputable
属性为total size
是否已知,即是否可用进度信息,loaded
属性为已经接收的字节数,total
为总字节数。该事件会在数据接收期间不断触发,但间隔不确定。
跨域CORS
提到XHR对象,我们就会讲到跨域问题,它是为了预防某些恶意行为的安全策略,但有时候我们需要跨域来实现某些功能。需要注意的是跨域并不仅仅是前端单方面的事情,它需要后端代码进行配合,我们只是通过一些方式跳过了浏览器的阻拦。
对那些可能对服务器数据产生副作用的 HTTP 请求方法(特别是 GET 以外的 HTTP 请求,或者搭配某些 MIME 类型的 POST 请求),浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨域请求。服务器确认允许之后,才发起实际的 HTTP 请求。
CORS(Cross-Origin Resource Sharing
, 跨域资源共享)的思想是浏览器和服务端通过头部信息来进行沟通确认是否给予响应。如:
如上就可以实现最简单的跨域访问,但是此时不能携带任何的cookie,如果我们需要传递cookie进行身份认证,需要设置:
这样我们就可以传递认证信息了,但如果允许认证,Access-Control-Allow-Origin不能设置为*,而一定是具体的域名信息。
现在的浏览器都对CORS有了实现,如IE使用XDomainRequest对象,其它浏览器使用XMLHttpRequest
对象。所以在此之前有很多奇技淫巧,如通过jsonp
/图像 Ping方法都不再详述,而且其都需要服务端配合并且有很多局限性。
IE实现: XDomainRequest
|
|
XDR区别于普通XHR:
通过这些区别可以阻止一部分的CSRF(Cross-Site Request Forgery,跨站点请求伪造)和XSS(Cross-Site Scripting,跨站点脚本)。
XDR与XHR的使用上非常相似,区别有几点:
其余浏览器实现: XMLHttpRequest
其余浏览器通过XHR对象直接实现了CORS,你只需要做的就是open()方法中传入一个绝对URL。
xhr.open('get', 'http://www.site.com/page', true);
相对于普通的XHR对象,CORS-XHR依然有部分限制:
其余跨域方法
上面的两种方法已经很成熟了,但是仍然有一部分方法可以跨域,比如图像Ping:
这种方式常用于服务端统计广告的点击次数,其缺陷为:
另外还有JSONP:
这种方式通过和服务器配合,跨域请求一个js文件并被服务器处理后传回:
handleResponse({'name': 'Nicholas'});
然后直接在浏览器调用了该函数,传回的数据被当做response形参进行处理。但它也有一些缺陷:
访问的方式是请求js,所以如果域名不安全,则很容易被恶意代码直接执行并攻击
无法检测是否错误,因为js不支持这样的接口事件,只能超时判断
上面两种方式很容易看出,我们在支持CORS之前,使用的方法只不过是采用img/css/js等不受跨域访问限制的对象,变相拿到了响应数据,但都有缺陷,所以如果没有历史包袱,建议采用XDR或XHR对象来实现跨域访问。