Ajax 与 Comet

来源:互联网 发布:iphone6导出视频mac 编辑:程序博客网 时间:2024/05/16 03:34

就叫 Ajax,即 Asynchronous JavaScript + XML ,异步JS+XML。
这一技术能够向服务器请求额外的数据而无须卸载页面。改变自从 Web 诞生以来就一直沿用的“单击,等待”的交互模式。

Ajax 技术的核心是 XMLHttpRequest 对象(简称 XHR),XHR 为向服务器发送请求和解析服务器响应提供了流畅的接口。能够以异步方式从服务器取得信息,意味着用户单击后,可以不必刷新页面也能取得新数据。 也就是说,可以使用 XHR 对象取得新数据,然后再通过 DOM 将新数据插入到页面中。

另外,虽然名字中包含 XML 的成分,但 Ajax 通信与数据格式无关;这种技术就是无须刷新页面即可从服务器取得数据,但不一定是 XML 数据。

XMLHttpRequest对象

IE7+、Firefox、Opera、Chrome 和 Safari 都支持原生的 XHR 对象,在这些浏览器中创建 XHR 对象要像下面这样使用 XMLHttpRequest 构造函数。

var xhr = new XMLHttpRequest();

XHR的用法

在使用 XHR 对象时,要调用的第一个方法是 open(),它接受 3 个参数:
1、要发送的请求的类型(“get”、”post”等)
2、请求的 URL
3、是否异步发送请求的布尔值。
下面就是调用这个方法的例子。

 xhr.open("get", "example.php", false);

这行代码会启动一个针对 example.php 的 GET 请求。
有关这行代码,需要说明两点:一是 URL 是相对路径,它相对于于执行代码的当前页面(当然也可以使用绝对路径);二是调用 open()方法并不会真正发送请求, 而只是启动一个请求以备发送。

并且, 只能向同一个域中使用相同端口和协议的 URL 发送请求。如果 URL 与启动请求的页面的域有任何差别,都会引发安全错误。也就是说,跨域问题是Ajax的短板。

要发送特定的请求,必须像下面这样调用 send()方法

xhr.open("get", "example.txt", false);xhr.send(null);

这里的 send() 方法接收一个参数,即要作为请求主体发送的数据。如果不需要通过请求主体发送数据,则必须传入 null,因为这个参数对有些浏览器来说是必需的。
调用 send()之后,请求就会被分派到服务器。

在收到服务器的响应后,响应的数据会自动填充 XHR 对象的属性,相关的属性简介如下:
1. responseText:作为响应主体被返回的文本。
2. responseXML:如果响应的内容类型是”text/xml”或”application/xml”,这个属性中将保存包含着响应数据的 XML DOM 文档。
3. status:响应的 HTTP 状态。
4. statusText:HTTP 状态的说明。

在接收到响应后,第一步是检查 status 属性,以确定响应已经成功返回。
一般来说,可以将 HTTP 状态代码为 200 作为成功的标志。此时,responseText 属性的内容已经就绪,而且在内容类型正确的情况下,responseXML 也应该能够访问了。
此外,状态代码为 304 表示请求的资源并没有被修改,可以直接使用浏览器中缓存的版本;当然,也意味着响应是有效的。

为确保接收到适当的响应,应该像下面这样检查上述这两种状态代码:

if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){      alert(xhr.responseText);  } else {      alert("Request was unsuccessful: " + xhr.status);}

注意:无论内容类型是什么,响应主体的内容都会保存到 responseText 属性中;而对于非 XML 数据而言,responseXML 属性的值将为 null。

多数情况下,我们要发送的是异步请求,这样才能让 JavaScript 继续执行而不必等待响应。此时,可以检测 XHR 对象的 readyState 属性,该属性表示请求/响应过程的当前活动阶段。
这个属性可取的值如下。
0:未初始化。尚未调用 open()方法。
1:启动。已经调用 open()方法,但尚未调用 send()方法。
2:发送。已经调用 send()方法,但尚未接收到响应。
3:接收。已经接收到部分响应数据。
4:完成。已经接收到全部响应数据,而且已经可以在客户端使用了。

只要 readyState 属性的值由一个值变成另一个值,都会触发一次 readystatechange 事件。可以利用这个事件来检测每次状态变化后 readyState 的值。通常,我们只对 readyState 值为 4 的阶段感兴趣,因为这时所有数据都已经就绪。

最好在调用 open()之前指定 onreadystatechange 事件处理程序,这样可以确保跨浏览器兼容性。

另外,在接收到响应之前还可以调用 abort()方法来取消异步请求,如下所示:

xhr.abort();

调用这个方法后,XHR 对象会停止触发事件,而且也不再允许访问任何与响应有关的对象属性。在 终止请求之后,还应该对 XHR 对象进行解引用操作。

HTTP头部信息

默认情况下,在发送 XHR 请求的同时,还会发送下列头部信息。

Accept:浏览器能够处理的内容类型。
Accept-Charset:浏览器能够显示的字符集。
Accept-Encoding:浏览器能够处理的压缩编码。
Accept-Language:浏览器当前设置的语言。
Connection:浏览器与服务器之间连接的类型。
Cookie:当前页面设置的任何 Cookie。
Host:发出请求的页面所在的域 。
Referer:发出请求的页面的 URI。注意,HTTP 规范将这个头部字段拼写错了,而为保证与规
范一致,也只能将错就错了。(这个英文单词的正确拼法应该是 referrer。)
User-Agent:浏览器的用户代理字符串。

虽然不同浏览器实际发送的头部信息会有所不同,但以上列出的基本上是所有浏览器都会发送的。
使用 setRequestHeader()方法可以设置自定义的请求头部信息。这个方法接受两个参数:头部字段的名称和头部字段的值。要成功发送请求头部信息,必须在调用 open()方法之后且调用 send()方法之前调用 setRequestHeader()。

xhr.open("get", "example.php", true); xhr.setRequestHeader("MyHeader", "MyValue"); xhr.send(null);

我们建议读者使用自定义 的头部字段名称,不要使用浏览器正常发送的字段名称,否则有可能会影响服务器的响应。

此外,有的浏览器允许开发人员重写默认的头部信息,但有的浏览器则不允许这样做。

调用 XHR 对象的 getResponseHeader()方法并传入头部字段名称,可以取得相应的响应头部信息。而调用 getAllResponseHeaders()方法则可以取得一个包含所有头部信息的长字符串。

GET请求

GET 是最常见的请求类型,最常用于向服务器查询某些信息。
必要时,可以将查询字符串参数追加到 URL 的末尾,以便将信息发送给服务器。对 XHR 而言,位于传入 open()方法的 URL 末尾的查询字符串必须经过正确的编码才行。

//编码函数function addURLParam(url, name, value) {    url += (url.indexOf("?") == -1 ? "?" : "&");    url += encodeURIComponent(name) + "=" + encodeURIComponent(value);    return url;}

POST请求

使用频率仅次于 GET 的是 POST 请求,通常用于向服务器发送应该被保存的数据。
POST 请求应该把数据作为请求的主体提交,而 GET 请求传统上不是这样。POST 请求的主体可以包含非常多的数据, 而且格式不限。在 open()方法第一个参数的位置传入”post”,就可以初始化一个 POST 请求。

发送 POST 请求的第二步就是向 send() 方法中传入某些数据。由于 XHR 最初的设计主要是为了处理 XML,因此可以在此传入 XML DOM 文档,传入的文档经序列化之后将作为请求主体被提交到服务器。
当然,也可以在此传入任何想发送到服务器的字符串。

//使用 XHR 来模仿表单提交xhr.open("post", "postexample.php", true); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); var form = document.getElementById("user-info"); xhr.send(serialize(form));

XMLHttpRequest 2 级

XMLHttpRequest 2 级则进一步发展了 XHR。并非所有浏览器都完整地实现了 XMLHttpRequest 2 级规范,但所有浏览器都实现了它规定的部分内容。

FormData

FormData 为序列化表单以及创建与表单格式相同的数据(用于通过 XHR 传输)提供了便利。
可以以键值对的方式向FormData对象中注入数据:

var data = new FormData();data.append("name", "Nicholas");

而通过向 FormData 构造函数中传入表单元素,可以利用表单元素的数据向其中填入键值对儿:

var data = new FormData(document.forms[0]);

创建了 FormData 的实例后,就可以将它直接传给 XHR 的 send() 方法。

var form = document.getElementById("user-info");xhr.send(new FormData(form));

使用 FormData 的方便之处体现在,不必像使用serialize方法时那样在 XHR 对象上设置请求头部。XHR 对象能够识别传 入的数据类型是 FormData 的实例,并配置适当的头部信息。

进度事件

这些进度事件都是和客户端与服务器通信状态有关的事件,最早其实只针对 XHR 操作,但目前也被其他 API 借鉴。这些事件共有6个,如下所列:

  1. loadstart:在接收到响应数据的第一个字节时触发。
  2. progress:在接收响应期间持续不断地触发。
  3. error:在请求发生错误时触发。
  4. abort:在因为调用 abort() 方法而终止连接时触发。
  5. load:在接收到完整的响应数据时触发。
  6. loadend:在通信完成或者触发 error、abort 或 load 事件后触发。

每个请求都从触发 loadstart 事件开始,接下来是一或多个 progress 事件,然后触发 error、abort 或 load 事件中的一个,最后以触发 loadend 事件结束。

支持前 5 个事件的浏览器有 Firefox 3.5+、Safari 4+、Chrome、iOS 版 Safari 和 Android 版 WebKit。
Opera(从第 11 版开始)、IE 8+只支持 load 事件。目前还没有浏览器支持 loadend 事件。

跨源资源共享

默认情况下,XHR 对象只能访问与包含它的页面位于同一个域中的资源。这种安全策略可以预防某些恶意行为。但是,实现合理的跨域请求对开发某些浏览器应用程序也是至关重要的。

CORS(Cross-Origin Resource Sharing,跨源资源共享)是 W3C 的一个工作草案,定义了在必须访问跨源资源时,浏览器与服务器应该如何沟通。

IE对CORS的实现

微软在 IE8 中引入了 XDR(XDomainRequest)类型。这个对象与 XHR 类似,但能实现安全可靠的跨域通信。

XDR 对象的使用方法与 XHR 对象非常相似。也是创建一个 XDomainRequest 的实例,调用 open() 方法,再调用 send()方法。但与 XHR 对象的 open()方法不同,XDR 对象的 open()方法只接收两个参数:请求的类型和 URL。

所有 XDR 请求都是异步执行的,不能用它来创建同步请求。

// 使用示例:var xdr = new XDomainRequest();xdr.onload = function(){    alert(xdr.responseText);};xdr.open("get", "http://www.somewhere-else.com/page/");xdr.send(null);

但是XDR和XHR也是有区别的,跨域请求的限制会更加严格:

cookie 不会随请求发送,也不会随响应返回。
只能设置请求头部信息中的 Content-Type 字段。
不能访问响应头部信息。
只支持GET和POST请求。

其他浏览器对CORS的实现

Firefox 3.5+、Safari 4+、Chrome、iOS 版 Safari 和 Android 平台中的 WebKit 都通过 XMLHttpRequest 对象实现了对 CORS 的原生支持。在尝试打开不同来源的资源时,无需额外编写代码就可以触发这个行为。要请求位于另一个域中的资源,使用标准的 XHR 对象并在 open() 方法中传入绝对 URL 即可。但是需要注意的是,跨域去访问的服务器必须支持cors,否则,也是无法得到资源的。

但是同样,跨域获取资源也有一些限制:

不能使用 setRequestHeader()设置自定义头部。
不能发送和接收 cookie。
调用 getAllResponseHeaders()方法总会返回空字符串。也就是不能获得头部信息。

Preflighted Reqeusts

预检请求(Preflighted requests ),它允许浏览器在跨域请求中询问服务器对跨域请求的支持情况。

当开发人员使用自定义的头部、 GET 或 POST 之外的方法,以及不同类型的主体内容来发送请求时,浏览器就会自动向服务器发送一个 Preflight 请求。

这种请求使用 OPTIONS 方法,发送下列头部。

Origin:与简单的请求相同。
Access-Control-Request-Method:请求自身使用的方法。
Access-Control-Request-Headers:(可选)自定义的头部信息,多个头部以逗号分隔。

发送这个请求后,服务器可以决定是否允许这种类型的请求。服务器通过在响应中发送头部信息与浏览器进行沟通。

如果满足浏览器发送信息的需求,浏览器会自动发起提交之前没提交的数据,否则浏览器拒绝提交数据。

其他跨域技术

图像Ping

原理:使用< img >标签,一个网页可以从任何网页中加载图像,不用担心跨域问题。

动态创建图像经常用于图像 Ping:图像 Ping 是与服务器进行简单、单向的跨域通信的一种方式。
请求的数据是通过查询字符串形式发送的,而响应可以是任意内容,但通常是像素图或 204 响应。

栗子:

var img = new Image();img.onload = img.onerror = function(){    alert("Done!");};img.src = "http://www.example.com/test?name=Nicholas";

图像 Ping 有两个主要的缺点,一是只能发送 GET 请求,二是无法访问服务器的响应文本。因此,图像 Ping 只能用于浏览器与服务器间的单向通信。

JSONP

JSONP 是通过动态< script >元素来使用的,使用时可以为 src 属性指定一个跨域 URL。这里的< script >元素与< img >元素类似,都有能力不受限制地从其他域加载资源。来看一个栗子:

function handleResponse(response){    alert("You’re at IP address " + response.ip + ", which is in " + response.city + ", " + response.region_name);}var script = document.createElement("script");script.src = "http://freegeoip.net/json/?callback=handleResponse"; document.body.insertBefore(script, document.body.firstChild);

在script标签的src中指定了callback函数为handleResponse,接下来当数据从服务器返回时,就会直接作为handleResponse的参数,供开发人员使用。与图像 Ping 相比,它的优点在于能够直接访问响应文本,支持在浏览器与服务器之间双向通信。

但是也有不足:
1、存在安全问题。
2、要确定 JSONP 请求是否失败并不容易。虽然 HTML5 给< script >元素新增了一个 onerror 事件处理程序,但目前还没有得到任何浏览器支持。开发人员只能使用定时器来检测是否得到响应。

Comet

Ajax 是一种从页面向服务器请求数据的技术,而 Comet 则是一种服务器向页面推送数据的技术。Comet 能够让信息近乎实时地被推送到页面上,非常适合处理体育比赛的分数和股票报价。

有两种实现 Comet 的方式:长轮询和流。

长轮询是传统轮询的一个翻版,即浏览器定时向服务器发送请求,看有没有更新的数据。页面发起一个到服务器的请求,然后服务器一直保持连接打开,直到有数据可发送。发送完数据之后,浏览器关闭连接,随即又发起一个到服务器的新请求。这一过程在页面打开期间一直持续不断。
轮询的优势是所有浏览器都支持,因为使用 XHR 对象和 setTimeout() 就能实现。而你要做的就是决定什么时候发送请求。

第二种流行的 Comet 实现是 HTTP 流。流不同于轮询,因为它在页面的整个生命周期内只使用一个 HTTP 连接。具体来说,就是浏览器向服务器发送一个请求,而服务器保持连接打开,然后周期性地向浏览器发送数据。
在 Firefox、Safari、Opera 和 Chrome 中,通过侦听 readystatechange 事件及检测 readyState 的值是否为 3,就可以利用 XHR 对象实现 HTTP 流。在上述这些浏览器中,随着不断从服务器接收数据,readyState 的值会周期性地变为 3。当 readyState 值变为 3 时,responseText 属性中就会保存接收到的所有数据。此时,就需要比较此前接收到的数据,决定从什么位置开始取得最新的数据。

xhr.onreadystatechange = function(){    var result;    if (xhr.readyState == 3){        //只取得最新数据并调整计数器        result = xhr.responseText.substring(received);        received += result.length; 21        //调用 progress 回调函数         progress(result);    } else if (xhr.readyState == 4){        finished(xhr.responseText);    }};

服务器发送事件

SSE(Server-Sent Events,服务器发送事件)是围绕只读 Comet 交互推出的 API 或者模式。SSE API 用于创建到服务器的单向连接,服务器通过这个连接可以发送任意数量的数据。

SSE 支持短轮询、长轮询和 HTTP 流,而且能在断开连接时自动确定何时重新连接。有了这么简单实用的 API,实现 Comet 就容易多了。

//预定一个新的事件流var source = new EventSource("myevents.php");

Web Sockets

Web Sockets 的目标是在一个单独的持久连接上提供全双工、双向通信

在 JavaScript 中创建了 Web Socket 之后,会有一个 HTTP 请求发送以发起连接。在取得服务器响应后,建立的连接会升级为 Web Socket 协议。也就是说,使用标准的 HTTP 服务器无法实现 Web Sockets,只有支持这种协议的专门服务器才能正常工作。

由于 Web Sockets 使用了自定义的协议,所以 URL 模式也略有不同。未加密的连接不再是 http://,而是 ws://;加密的连接也不是 https://,而是 wss://。

使用自定义协议而非 HTTP 协议的好处是,能够在客户端和服务器之间发送非常少量的数据,而不必担心 HTTP 那样字节级的开销。

使用自定义协议的缺点在于,制定协议的时间比制定 JavaScript API 的时间还要长。Web Sockets 曾几度搁浅,就因为不断有人发现这个新协议存在一致性和安全性的问题。

创建 Web Socket:

var socket = new WebSocket("ws://www.example.com/server.php");

在考虑是使用 SSE 还是使用 Web Sockets 时,可以考虑如下几个因素。
第一, 你是否有自由度建立和维护 Web Sockets 服务器?因为 Web Socket 协议不同于 HTTP,所以现有服务器 不能用于 Web Socket 通信。SSE 倒是通过常规 HTTP 通信,因此现有服务器就可以满足需求。

第二、到底需不需要双向通信。如果用例只需读取服务器数据(如比赛成绩),那么 SSE 比较容易实现。如果用例必须双向通信(如聊天室),那么 Web Sockets 显然更好。另外,在不能选择 Web Sockets 的情况下,组合 XHR 和 SSE 也是能实现双向通信的。

0 0
原创粉丝点击