第21章 Ajax和Comet
1.Ajax 是无需刷新页面就能够从服务器取得数据的一种方法,核心对象是 XMLHttpRequest(XHR)对象。
2.兼容各个浏览器创建XHR对象的方法
function createXHR(){
if (typeof XMLHttpRequest != "undefined"){
return new XMLHttpRequest();
} else if (typeof ActiveXObject != "undefined"){
//用于兼容IE7之前的版本
if (typeof arguments.callee.activeXString != "string"){
var versions = [ "MSXML2.XMLHttp.6.0", "MSXML2.XMLHttp.3.0","MSXML2.XMLHttp"],
i, len;
for (i=0,len=versions.length; i < len; i++){
try {
new ActiveXObject(versions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex){
//跳过
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
} else {
throw new Error("No XHR object available.");
}
}
3.使用 XHR 对象时,要调用的第一个方法是 open(),接受 3 个参数:
- 要发送的请求的类型
- 请求的 URL
- 表示是否异步发送请求的布尔值
xhr.open("get", "example.php", false);
调用 open()方法并不会真正发送请求, 而只是启动一个请求以备发送,只能向同一个域中使用相同端口和协议的 URL 发送请求。
4.使用 XHR 对象时,要调用的第二个方法是send(),接收一个参数,即要作为请求主体发送的数据。
如果不需要通过请求主体发送 数据,则必须传入 null,因为这个参数对有些浏览器来说是必需的。调用 send()之后,请求就会被分派到服务器。
xhr.open("get", "example.txt", false);
xhr.send(null);
5.响应的数据会自动填充 XHR 对象的属性,相关的属性如下:
- responseText:作为响应主体被返回的文本。
- responseXML:如果响应的内容类型是"text/xml"或"application/xml",这个属性中将保存包含着响应数据的 XML DOM 文档。
- status:响应的 HTTP 状态。
- statusText:HTTP 状态的说明。
6.在接收到响应后:
第一步是检查 status 属性,将 HTTP 状态代码 200 作为成功的标志或者状态代码为 304 表示请求的资源并没有被修改,可以直接使用浏览器中缓存的版本。
(同时多数情况下,我们还是要发送异步请求,才能让 JavaScript 继续执行而不必等待响应。)
此时,可以检测 XHR 对象的 readyState 属性,该属性表示请求 /响应过程的当前活动阶段。这个属性可取的值如下:
0:未初始化。尚未调用open()
1:启动。已经调用open(),但未调用send()
2:发送。已经调用send(),但尚未接收到响应。
3:接收。已经接收到部分响应数据。
4:完成。已经接收到全部响应数据,而且已经可以再客户端使用了。
7.默认情况下,在发送 XHR 请求的同时,还会发送下列头部信息:
- Accept:浏览器能够处理的内容类型。
- Accept-Charset:浏览器能够显示的字符集。
- Accept-Encoding:浏览器能够处理的压缩编码。
- Accept-Language:浏览器当前设置的语言。
- Connection:浏览器与服务器之间连接的类型。
- Cookie:当前页面设置的任何 Cookie。
- Host:发出请求的页面所在的域 。
- Referer:发出请求的页面的 URI。注意,HTTP 规范将这个头部字段拼写错了,而为保证与规范一致,也只能将错就错了。(这个英文单词的正确拼法应该是 referrer。)
- User-Agent:浏览器的用户代理字符串。
8.setRequestHeader()方法可以设置自定义的请求头部信息,接受两个参数:头部字段的名称和头部字段的值。要成功发送请求头部信息,必须在调用 open()方法之后且调用 send()方法之前调用。
var xhr = createXHR();
xhr.open("get", "example.php", true);
xhr.setRequestHeader("MyHeader", "MyValue");
xhr.send(null);
getResponseHeader()方法并传入头部字段名称,可以取得相应的响应头部信息。
var myHeader = xhr.getResponseHeader("MyHeader");
getAllResponseHeaders()方法则可以取得一个包含所有头部信息的长字符串。
9.GET 是最常见的请求类型,最常用于向服务器查询某些信息。必要时,可以将查询字符串参数追加 到 URL 的末尾,以便将信息发送给服务器。查询字符串中每个参数的名 称和值都必须使用 encodeURIComponent()进行编码,然后才能放到 URL 的末尾;而且所有名-值对 儿都必须由和号(&)分隔。
下面函数可以辅助向现有 URL 的末尾添加查询字符串参数,接受三个参数:要添加参数的 URL、参数的名称和参数的值。
function addURLParam(url, name, value) {
url += (url.indexOf("?") == -1 ? "?" : "&");
url += encodeURIComponent(name) + "=" + encodeURIComponent(value);
return url;
}
10.POST 请求,通常用于向服务器发送应该被保存的数据。发送 POST 请求的第二步就是向 send()方法中传入某些数据。
11.FormData 类型为序列化表单以及创建与表单格式相同的数据(用于通过 XHR 传输)提供了便利。
//创建了一个 FormData 对象,并向其中添加了一些数据,append()方法接收两个参数:键和值,分别对应表单字段的名字和字段中包含的值。
var data = new FormData();
data.append("name", "Nicholas");
//通过向 FormData 构造函数中传入表单元素,也可以用表单元素的数据 预先向其中填入键值对儿
var data = new FormData(document.forms[0]);
12.overrideMimeType()方法,用于重写 XHR 响应的 MIME 类型。调用 overrideMimeType()必须在 send()方法之前,才能保证重写响应的 MIME 类型。
var xhr = createXHR();
xhr.open("get", "text.php", true);
xhr.overrideMimeType("text/xml");
xhr.send(null);
13.有 6 个进度事件,每个请求都从触发 loadstart 事件开始,接下来是一或多个 progress 事件,然后触发 error、abort 或 load 事件中的一个,最后以触发 loadend 事件结束:
- loadstart:在接收到响应数据的第一个字节时触发。
- progress:在接收响应期间持续不断地触发。
- error:在请求发生错误时触发。
- abort:在因为调用 abort()方法而终止连接时触发。
- load:在接收到完整的响应数据时触发。
- loadend:在通信完成或者触发 error、abort 或 load 事件后触发。
14.progress 事件会在浏览器接收新数据期间周期性地触发。onprogress 事件处理程序会接收到一个 event 对象,其 target 属性是 XHR 对象,包含着三个额外的属性:lengthComputable、position 和 totalSize。其中,lengthComputable 是一个表示进度信息是否可用的布尔值,position 表示已经接收的字节数,totalSize 表示根据 Content-Length 响应头部确定的预期字节数。为用户创建一个进度指示器:
var xhr = createXHR();
xhr.onload = function(event){
if ((xhr.status >= 200 && xhr.status < 300) ||xhr.status == 304){
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
};
xhr.onprogress = function(event){
var divStatus = document.getElementById("status");
if (event.lengthComputable){
divStatus.innerHTML = "Received " + event.position + " of "+
event.totalSize+" bytes";
}
};
xhr.open("get", "altevents.php", true);
xhr.send(null);
15.默认情况下,XHR 对象只能访问与包含它的页面位于同一个域中的资源。CORS(Cross-Origin Resource Sharing,跨源资源共享),定义了在必须访问跨源资源时,浏览器与服务器应该如何沟通。CORS 背后的基本思想,就是使用自定义的 HTTP 头部 让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。
16.浏览器对 CORS 的支持程度并不都一样,但所有浏览器都支持简单的(非 Preflight 和不带凭据 的)请求,因此有必要实现一个跨浏览器的方案。检测 XHR(Firefox 3.5+、Safari 4+、Chrome、iOS 版 Safari 和 Android 平台中的 WebKit 都通过 XMLHttpRequest 对象实现了对 CORS 的原生支持。) 是否支持 CORS 的最简单方式,就是检查是否存在 withCredentials 属性( 通过将 withCredentials 属性设置为 true,可以指定某个请求应该发送凭据)。再结合检测 XDomainRequest 对象(检测IE)是否存在,就可以兼顾所有浏览器了。
function createCORSRequest(method, url){
var xhr = new XMLHttpRequest();
if ("withCredentials" in xhr){
xhr.open(method, url, true);
} else if (typeof XDomainRequest != "undefined"){
xhr = new XDomainRequest();
xhr.open(method, url);
} else {
xhr = null;
}
return xhr;
}
var request = createCORSRequest("get", "http://www.somewhere-else.com/page/");
if (request){
request.onload = function(){
//对 request.responseText 进行处理
};
request.send();
}
17.图像 Ping 最常用于跟踪用户点击页面或动态广告曝光次数,有两个主要的缺点,一是只能发送 GET 请求,二是无法访问服务器的响应文本。因此,图像 Ping 只能用于浏览器与服务器间的单向通信。通过 图像 Ping,浏览器得不到任何具体的数据,通过侦听 load 和 error 事件,能知道响应是什么时候接收到的。
/*创建了一个 Image 的实例,然后将 onload 和 onerror 事件处理程序指定为同一个函数,只要请求完成,就能得到通知。
请求从设置 src 属性那一刻开始。
这个例子在请求中发送了一个 name 参数。*/
var img = new Image();
img.onload = img.onerror = function(){
alert("Done!");
};
img.src = "http://www.example.com/test?name=Nicholas";
18.JSONP(JSON with padding/填充式 JSON 或参数式 JSON)由两部分组成:回调函数和数据。
- 回调函数:响应到来时应该在页面中调用的函数,名字一般是在请求中指定的。
- 数据:传入回调函数中的 JSON 数据。
优点:能够直接访问响应文本,支持在浏览器与服务器之间双向通信。
不足:1⃣️从其他域中加载代码执行。如果其他域不安全,很可能会在响应中夹带一些恶意代码
2⃣️确定 JSONP 请求是否失败并不容易
JSONP 是通过动态<script>元素来使用的,使用时可以为src 属性指定一个跨域 URL。因为 JSONP 是有效的 JavaScript 代码,所以在请求完成后,即在 JSONP 响应加载到页面中以后,就会立即执行。
//通过查询地理定位服务来显示你的 IP 地址和位置信息。
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);
19.Comet :一种服务器向页面推送数据的技术,能够让信息近乎实时地被推送到页面上,非常适合处理体育比赛的分数和股票报价。实现 Comet 的手段 主要有两个:长轮询和 HTTP 流。
- 长轮询:页面发起一个到服务器的请求,然后服务器一直保持连接打开,直到有数据可发送。发送完数据之后,浏览器关闭连接,随即又发起一个到服务器的新请求。所有浏览器都支持
- HTTP 流:浏览器向服务器发送一个请求,而服务器保持连接打开,然后周期性地向浏览器发送数据。
-
/*createStreamingClient()函数接收三个参数:要连接的 URL、在接收到数据时调用的函 数以及关闭连接时调用的函数。 监听readyState 值为 3,就对 responseText 进行分割以取得最新数据。(当 readyState 值变为 3 时,responseText 属性中就会保 存接收到的所有数据。) 这里的 received 变量用于记录已经处理了多少个字符,每次 readyState 值为 3 时都递增。 然后,通过 progress 回调函数来处理传入的新数据。 而当 readyState 值为 4 时,则执行 finished 回调函数,传入响应返回的全部内容。 */ function createStreamingClient(url, progress, finished){ var xhr = new XMLHttpRequest(), received = 0; xhr.open("get", url, true); xhr.onreadystatechange = function(){ var result; if (xhr.readyState == 3){ //只取得最新数据并调整计数器 result = xhr.responseText.substring(received); received += result.length; //调用 progress 回调函数 progress(result); } else if (xhr.readyState == 4){ finished(xhr.responseText); } }; xhr.send(null); return xhr; } var client = createStreamingClient("streaming.php", function(data){ alert("Received: " + data); }, function(data){ alert("Done!"); });
20.SSE(Server-Sent Events,服务器发送事件)API:用于创建到服务器的单向连接,服务器通过这个连接可以发送任意数量的数据。服务器响应的 MIME 类型必须是 text/event-stream,而且是浏览器中的 JavaScript API 能解析格式输出。
首先要创建一个新的 EventSource 对象,并传进一个入口点:
EventSource 对象会保持与服务器的活动连接。如果连接断开,还会重新连接,适合长轮询和 HTTP 流。
//传入的 URL 必须与创建对象的页面同源(相同的 URL 模式、域及端口)。
var source = new EventSource("myevents.php");
EventSource 的 实例有一个 readyState 属性:
- 值为 0 表示正连接到服务器
- 值为 1 表示打开了连接
- 值为 2 表示关闭 了连接。
EventSource 的 实例有三个事件:
- open:在建立连接时触发。
- message:在从服务器接收到新事件时触发。
- error:在无法建立连接时触发。
服务器发回的数据以字符串形式保存在 event.data 中
强制立即断开连接并且不再重新连接,可以调用 close() 方法。
source.close();
21.Web Sockets 是一种与服务器进行全双工、双向通信的信道。不使用 HTTP 协议,而使用一种自定义的协议,未加密是 ws://;加密的连接是 wss://,能够在客户端和服务器之间发送非常少量的数据,而不必担心 HTTP 那样字节级的开销,非常适合移动应用。
- 要创建 Web Socket,先实例一个 WebSocket 对象并传入要连接的 URL:
//必须给 WebSocket 构造函数传入绝对 URL
var socket = new WebSocket("ws://www.example.com/server.php");
- 实例化了 WebSocket 对象后,浏览器就会马上尝试创建连接。与 XHR 类似,WebSocket 也有一 个表示当前状态的 readyState 属性:
- WebSocket.OPENING (0):正在建立连接。
- WebSocket.OPEN (1):已经建立连接。
- WebSocket.CLOSING (2):正在关闭连接。
- WebSocket.CLOSE (3):已经关闭连接。
- Web Socket 打开之后,就可以通过 send()方法向服务器发送数据, 但是只能发送纯文本数据,所以对于复杂的数据结构,在发送之前, 必须进行序列化。
-
var message = { time: new Date(), text: "Hello world!", clientId: "asdfp8734rew" }; socket.send(JSON.stringify(message));
- 当服务器向客户端发来消息时,WebSocket 对象就会触发 message 事件,把返回的数据保存在 event.data 属性中。
-
//event.data 中返回的数据是字符串 socket.onmessage = function(event){ var data = event.data; //处理数据 };
- 关闭 Web Socket 连接,可以在任何时候调用 close()方法。
-
socket.close();
- WebSocket 对象还有其他三个事件,在连接生命周期的不同阶段触发:
- open:在成功建立连接时触发。
- error:在发生错误时触发,连接不能持续。
- close:在连接关闭时触发。
-
socket.onopen = function(){ alert("Connection established."); }; socket.onerror = function(){ alert("Connection error."); }; /*只有 close 事件的 event 对象有额外的信息,事件对象有三个额外的属性: wasClean:一个布尔值,表示连接是否已经明确地关闭; code:服务器返回的数值状态码; reason:一个字符串,包含服务器发回的消息。*/ socket.onclose = function(event){ console.log("Was clean? " + event.wasClean + " Code=" + event.code + "Reason="+ event.reason); };
22. CSRF(Cross-Site Request Forgery,跨站 点请求伪造):未被授权系统伪装自己,让处理请求的服务器认为它是合法的。为确保通过 XHR 访问的 URL 安全,通行的做法就是验证发送请求者是否有权限访问相应的资源。有下列几种方式可供选择:
- 要求以 SSL 连接来访问可以通过 XHR 请求的资源。
- 要求每一次请求都要附带经过相应算法计算得到的验证码。