高程3总结#第21章Ajax与Comet
Ajax与Comet
XMLHttpRequest对象
IE5是第一款引入XHR对象的浏览器,在IE5中,XHR对象是通过MSXML库中的一个ActiveX对象实现的
//适用于 IE7 之前的版本 function createXHR(){ 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); }
- 这个函数会尽力根据IE中可用的MSXML库的情况创建最新版本的XHR对象
IE7+、Firefox、Opera、Chrome、Safari都支持原生的XHR对象,这些浏览器中创建XHR对象,可以使用XMLHttpRequest构造函数
var xhr=new XMLHttpRequest();
如果还必须要支持IE的更早版本,可以在createHXR()函数中加入对原生XHR对象的支持
function createXHR(){ if (typeof XMLHttpRequest != "undefined"){ return new XMLHttpRequest(); } else if (typeof ActiveXObject != "undefined"){ 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."); } }
这个函数中新增的代码首先检测原生XHR对象是否存在,如果存在则返回它的新实例,如果原生对象不存在,则检测ActiveX对象,如果这两种对象都不存在,就抛出一个错误,然后就可以使用下面的代码在所有浏览器中创建XHR对象了
var xhr=createXHR();
XHR的用法
在使用XHR对象时,要调用的第一个方法是open(),接收3个参数:要发送的请求的类型(get或者post)、请求的URL和表示是否异步发送请求的布尔值
xhr.open("get","example.php",false);
- 这行代码会启动一个针对example.php的GET请求
- URL相对于执行代码的当前页面
- open()方法并不会真正发送请求,而是启动一个请求以备发送
要想发送特定的请求,调用send()方法
xhr.open("get","example.txt",false); xhr.send(null);
- send()方法接收一个参数,要作为请求主体发送的数据,如果不需要通过请求主体发送数据,必须传入null
响应的数据会自动填充XHR对象的属性
- responseText,作为响应主体被返回的文本
- responseXML,如果响应的内容类型是"text/xml"或"application/xml",这个属性中将保存包含着响应数据的XML DOM文档
- status,响应的HTTP状态
- statusText,HTTP状态的说明
XHR对象的readyState属性表示请求响应过程的当前活动阶段
- 0,未初始化,尚未调用open()方法
- 1,启动,已经调用open()方法,但尚未调用send()方法
- 2,发送,已经调用send()方法,但尚未接收到响应
- 3,接收,已经接收到部分响应数据
- 4,完成,已经接收到全部响应数据,而且已经可以在客户端使用了
必须在调用open()之前指定onreadystatechange事件处理程序才能确保跨浏览器兼容性
var xhr = createXHR(); xhr.onreadystatechange = function(){ if (xhr.readyState == 4){ if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ alert(xhr.responseText); } else { alert("Request was unsuccessful: " + xhr.status); } } }; xhr.open("get", "example.txt", true); xhr.send(null);
- 在接收到响应之前还可以调用abort()方法来取消异步请求
HTTP头部信息
在发送XHR请求的同时,还会发送下列头部信息
- Accept,浏览器能够处理的内容类型
- Accept-Charset,浏览器能够显示的字符集
- Accept-Encoding,浏览器能够处理的压缩编码
- Accept-Language,浏览器当前设置的语言
- Connection,浏览器与服务器之间连接的类型
- Cookie,当前页面设置的任何Cookie
- Host,发出请求的页面所在的域
- Referer,发出请求的页面的URI
- User-Agent,浏览器的用户代理字符串
setRequestHeader()方法,可以设置自定义的请求头部信息,这个方法接收两个参数:头部字段名称和头部字段的值。要成功发送头部信息,必须在调用open()方法之后且调用send()方法之前调用setRequestHeader()
var xhr = createXHR(); xhr.onreadystatechange = function(){ if (xhr.readyState == 4){ if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ alert(xhr.responseText); } else { alert("Request was unsuccessful: " + xhr.status); } } }; xhr.open("get", "example.php", true); xhr.setRequestHeader("MyHeader", "MyValue"); xhr.send(null);
GET请求
- GET是最常见的请求类型,最常用于向服务器查询某些信息,必要时可以将查询字符串参数追加到URI的末尾,以便将信息发送给服务器
- 使用GET请求经常会发生一个错误,就是查询字符串的格式问题。
查询字符串中每个参数的名称和值都必须使用encodeURIComponent()进行编码,然后才能放到URL的末尾,而且所有的名-值对都必须由&分隔
xhr.open("get","example.php?name1=value1&name2=value2",true)
向现有URL的末尾添加查询字符串参数
function addURLParam(url, name, value) { url += (url.indexOf("?") == -1 ? "?" : "&"); url += encodeURIComponent(name) + "=" + encodeURIComponent(value); return url; }
- addURLParam()函数接收3个参数:要添加参数的URL、参数的名称和参数的值
POST请求
POST请求,通常用于向服务器发送应该被保存的数据
function submitData(){ var xhr = createXHR(); xhr.onreadystatechange = function(){ if (xhr.readyState == 4){ if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ alert(xhr.responseText); } else { alert("Request was unsuccessful: " + xhr.status); } } }; 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级
FormData
FormData为序列化表单以及创建与表单格式相同的数据,提供了便利
var data=new FormData(); data.append("name","Nicholas");
- 这个append()方法接收两个参数,键和值
创建了FormData的实例后,可以将它直接传给XHR的send()方法
var xhr = createXHR(); xhr.onreadystatechange = function(){ if (xhr.readyState == 4){ if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ alert(xhr.responseText); } else { alert("Request was unsuccessful: " + xhr.status); } } }; xhr.open("post","postexample.php", true); var form = document.getElementById("user-info"); xhr.send(new FormData(form));
超时设定
IE8为XHR对象添加了一个timeout属性,表示请求在等待响应多少毫秒之后就会终止,在给timeout设置一个数值后,如果在规定的时间内浏览器还没有接收到响应,那么就会触发timeout事件,进而会调用ontimeout事件处理程序
var xhr = createXHR(); xhr.onreadystatechange = function(){ if (xhr.readyState == 4){ try { if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ alert(xhr.responseText); } else { alert("Request was unsuccessful: " + xhr.status); } } catch (ex){ //假设由 ontimeout 事件处理程序处理 } } }; xhr.open("get", "timeout.php", true); xhr.timeout = 1000; // 将超时设置为 1 秒钟(仅适用于 IE8+ ) xhr.ontimeout = function(){ alert("Request did not return in a second."); }; xhr.send(null);
overrideMimeType()方法
- Firefox最早引入了overrideMimeType()方法,用于重写XHR响应的MIME类型
通过调用overrideMimeType()方法,可以保证把响应当做XML而非纯文本来处理
var xhr = createXHR(); xhr.open("get", "text.php", true); xhr.overrideMimeType("text/xml"); xhr.send(null);
进度事件
6个进度事件
- loadstart,在接收到响应数据的第一个字节时触发
- progress,在接收响应期间持续不断地触发
- error,在请求发生错误时触发
- abort,在因为调用abort()方法而终止连接时触发
- load,在接收到完整的响应数据时触发
- loadend,在通信完成或者触发error、abort或load事件后触发
load事件
var xhr = createXHR(); xhr.onload = function(){ if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ alert(xhr.responseText); } else { alert("Request was unsuccessful: " + xhr.status); } }; xhr.open("get", "altevents.php", true); xhr.send(null);
progress事件
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);
- 为确保正常执行,必须在调用open()方法之前添加onprogress事件处理程序
跨源资源共享
- CORS,跨资源共享,定义了在必须访问跨源资源时,浏览器远服务器应该如何沟通。CORS基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是应该失败
IE对CORS的实现
微软在IE8中引入了XDR类型,这个对象与XHR类型,但是能实现安全可靠的跨域通信
- cookie不会随请求发布,也不会随响应返回
- 只能设置请求头部信息中的Content-Type字段
- 不能访问响应头部信息
- 只支持GET和POST请求
- XDR对象的使用方法与XHR对象非常相似,也是创建一个XDomainRequest的实例,调用open()方法,再调用send()方法,与XHR对象的open()方法不同,XDR对象的open()方法只接收2个参数:请求类型和URL
所有XDR请求都是异步执行的,不能用它来创建同步请求,请求返回之后,会触发load事件,响应的数据也会保存在responseText属性中
var xdr = new XDomainRequest(); xdr.onload = function(){ alert(xdr.responseText); }; xdr.open("get", "http://www.somewhere-else.com/page/"); xdr.send(null)
请求返回前调用abort()方法可以终止请求
xdr.abort();//终止请求
与XHR一样,XDR对象也支持timeout属性以及ontimeout事件处理程序
var xdr = new XDomainRequest(); xdr.onload = function(){ alert(xdr.responseText); }; xdr.onerror = function(){ alert("An error occurred."); }; xdr.timeout = 1000; xdr.ontimeout = function(){ alert("Request took too long."); }; xdr.open("get", "http://www.somewhere-else.com/page/"); xdr.send(null);
为支持POST请求,XDR对象提供了contentType属性,用来表示发送数据的格式
var xdr = new XDomainRequest(); xdr.onload = function(){ alert(xdr.responseText); }; xdr.onerror = function(){ alert("An error occurred."); }; xdr.open("post", "http://www.somewhere-else.com/page/"); xdr.contentType = "application/x-www-form-urlencoded"; xdr.send("name1=value1&name2=value2");
其他浏览器对CORS的实现
要请求位于另一个域中的资源,使用标准的XHR对象并在open()方法中传入绝对URL
var xhr = createXHR(); xhr.onreadystatechange = function(){ if (xhr.readyState == 4){ if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){ alert(xhr.responseText); } else { alert("Request was unsuccessful: " + xhr.status); } } }; xhr.open("get", "http://www.somewhere-else.com/page/", true); xhr.send(null);
安全限制
- 不能使用setRequsetHeader()设置自定义头部
- 不能发送和接收cookie
- 调用getAllResponseHeaders()方法总会返回空字符串
Preflighted Requests
- 通过Preflighted Request的透明服务器验证机制支持开发人员使用自定义的头部、GET和POST之外的方法,以及不同类型的主体内容
这种请求使用OPITONS方法,发送下列头部
- Origin,与简单的请求相同
- Access-Control-Method,请求自身使用的方法
- Access-Control-Headers,自定义的头部信息,多个头部以逗号分隔
Origin: http://www.nczonline.net Access-Control-Request-Method: POST Access-Control-Request-Headers: NCZ
服务器可以决定是否允许这种类型的请求,服务器在响应中发送如下头部与浏览器进行沟通
- Access-Control-Allow-Origin,与简单的请求相同
- Access-Control-Allow-Methods,允许的方法,多个方法以逗号分隔
- Access-Control-Allow-Headers,允许的头部,多个头部以逗号分隔
- Access-Control-Max-Age,应该将这个Preflight请求缓存多长时间,以秒表示
Access-Control-Allow-Origin: http://www.nczonline.net Access-Control-Allow-Methods: POST, GET Access-Control-Allow-Headers: NCZ Access-Control-Max-Age: 1728000
带凭据的请求
通过将withCredentials属性设置为true,可以指定某个请求应该发送凭据
Access-Control-Allow-Credentials:true
- 如果发送的是带凭据的请求,但服务器的响应中没有包含这个头部,那么浏览器就不会把响应交给JavaScript,于是responseText中将是空字符串,status的值为0,而且会调用onerror()事件处理程序
跨浏览器的CORS
function createCORSRequest(method, url){ var xhr = new XMLHttpRequest(); if ("withCredentials" in xhr){ xhr.open(method, url, true); } else if (typeof XDomainRequest != "undefined"){ vxhr = 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(); }
Firefox、Safari和Chrome中的XMLHttpRequest对象与IE中的XDomainRequest对象类似,都提供了够用的接口,这两个对象共同的属性方法如下
- abort(),用于停止正在进行的请求
- onerror(),用于替代onreadystatechange检测错误
- onload(),用于替代onreadystatechange检测成功
- responseText(),用于取得响应内容
- send(),用于发送请求
其他跨域技术
图像Ping
- 一个网页可以从任何网页中加载图像,不用担心跨域不跨域
通过图像Ping,浏览器得不到任何具体的数据,但通过侦听load和error事件,它能知道响应什么时候接收到的
var img = new Image(); img.onload = img.onerror = function(){ alert("Done!"); }; img.src = "http://www.example.com/test?name=Nicholas";
- 图像Ping最常用于跟踪用户点击页面或动态广告曝光次数,图像Ping有两个主要的缺点,一是只能发送GET请求,二是无法访问服务器的响应文本。因此图像Ping只能用于浏览器与服务器间的单向通信
JSONP
JSONP由两部分组成:回调函数和数据,回调函数是当响应到来时应该在页面中调用的函数,回调函数的名字一般是在请求中指定的,而数据就是传入回调函数中的JSON数据
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);
- 优点在于能够直接访问响应文本,支持在浏览器与服务器之间双向通信
Comet
两种实现Comet的方式
长轮询
- 短轮询时间线
- 长轮询把短轮询颠倒了一下。页面发起一个到服务器的请求,然后服务器一直保持连接打开,直到有数据可发送。发送完数据之后,浏览器关闭连接,随即又发起一个到服务器的新请求,这一过程在页面打开期间一直持续不断
- 短轮询时间线
流
在页面的整个生命周期内只使用一个HTTP连接,具体来说就是浏览器向服务器发送一个请求,而服务器保持连接打开,然后周期性的向浏览器发送数据
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!"); });
- 这个createStreamingClient()函数接收三个参数:要连接的URL、在接收到数据时调用的函数以及关闭连接时调用的函数
服务器发送事件
SSE是围绕只读Comet交互推出的API或者模式
var source=new EventSource("myevents.php");
- EventSource的实例有一个readyState属性,值为0表示正连接到服务器,值为1表示打开了连接,值为2表示关闭了连接
另外还有三个事件
- open,在建立连接时触发
- message,在从服务器接收到新事件时触发
- error,在无法建立连接时触发
Web Sockets
- Web Sockets的目标是在一个单独的持久连接上提供全双工、双向通信
要创建Web Socket,先实例一个WebSocket对象并传入要连接的URL
var socket = new WebSocket("ws://www.example.com/server.php");
实例化WebSocket对象后,浏览器会马上尝试创建连接,与XHR类似,WebSocket也有一个表示当前状态的readyState属性,这个属性的值与XHR并不相同
- WebSocket.OPENING(0),正在建立连接
- WebSocket.OPEN(1),已经建立连接
- WebSocket.CLOSING(2),正在关闭连接
- WebSocket.CLOSE(3),已经关闭连接
- WebSocket没有readystatechange事件,不过有其他事件,对应着不同的状态,readyState的值永远从0开始
要关闭Web Socket连接,可以在任何时候调用close()方法
socket.close();
- 调用close()之后,readyState的值立即变为2,而关闭连接后就会变成3
使用send()方法并传入任意字符串
var socket = new WebSocket("ws://www.example.com/server.php"); socket.send("Hello world!"); //将数据序列化为JSON字符串,然后发送到服务器 var message = { time: new Date(), text: "Hello world!", clientId: "asdfp8734rew" }; socket.send(JSON.stringify(message)); //当服务器收到消息时,WebSocket对象就会触发message事件,这个message事件与其他传递消息的协议类似,也是把返回的数据保存在event.data属性中 socket.onmessage = function(event){ var data = event.data; //处理数据 };
其他事件
- open,在成功建立连接时触发
- error,在发生错误时触发,连接不能持续
- close,在连接关闭时触发
var socket = new WebSocket("ws://www.example.com/server.php"); socket.onopen = function(){ alert("Connection established."); }; socket.onerror = function(){ alert("Connection error."); }; socket.onclose = function(){ alert("Connection closed."); };
SSE与Web Sockets
考虑是使用 SSE 还是使用 Web Sockets 时,可以考虑如下几个因素。
- 首先,你是否有自由度建立和维护 Web Sockets服务器?因为 Web Socket 协议不同于 HTTP,所以现有服务器不能用于 Web Socket 通信。SSE 倒是通过常规 HTTP 通信,因此现有服务器就可以满足需求。
- 第二个要考虑的问题是到底需不需要双向通信。如果用例只需读取服务器数据(如比赛成绩),那么 SSE 比较容易实现。如果用例必须双向通信(如聊天室),那么 Web Sockets 显然更好。在不能选择 Web Sockets 的情况下,组合 XHR 和 SSE 也是能实现双向通信的。
安全
为确保通过 XHR 访问的 URL 安全,通行的做法就是验证发送请求者是否有权限访问相应的资源
- 要求以 SSL 连接来访问可以通过 XHR 请求的资源。
- 要求每一次请求都要附带经过相应算法计算得到的验证码。请注意,下列措施对防范 CSRF 攻击不起作用。
- 要求发送 POST 而不是 GET 请求——很容易改变。
- 检查来源 URL 以确定是否可信——来源记录很容易伪造。
- 基于 cookie 信息进行验证——同样很容易伪造
相关推荐
结束数据方法的参数,该如何定义?-- 集合为自定义实体类中的结合属性,有几个实体类,改变下标就行了。<input id="add" type="button" value="新增visitor&quo