ajax和comet
一,XMLHttpRequest对象
IE5是最早引入XHR对象的浏览器,XHR对象是通过MSXML库中的一个ActiveX对象实现的
使用MSXML库中的XHR对象,编写一个函数如下
function ceateXHR(){ if(typeof argument.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(argument.callee.activeXString); }
IE7+,Firefox,Opera,CHrome和safari中支持原生XHR对象,
var xhr = new XMLHttpRequest();
为了兼容以上所有,使用下列函数
function createXHR(){ if(typeof XMLHttpRequest != "undefined"){ return new XMLHttpRequest(); }else if(typeof ActiveXobject != "undefined"){ if(typeof argument.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]); argument.callee.activeXString = versions[i]; break; }catch(ex){ //跳过 } } } return new ActiveXObject(argument.callee.activeXString); }else{ throw new Error("No XHR object available"); } }
创建对象,var xhr = createXHR();
1,XHR的用法
open(请求的类型(get或post),请求的URL,是否异步发送的布尔值),
要点:URL相对于执行代码的当前页面(可以使用绝对路径),open方法不会真的发送请求只是启动一个请求以备发送
send(作为请求主体发送的数据),发送特定的请求,可以传人null,调用函数后请求会被派送到服务器
如果请求时同步的,js代码会等到服务器响应之后执行,响应的数据会自动填充XHR对象的属性
responseText:作为响应主体被返回的文本
responseXML:如果响应的内容类型是text/xml,application/xml,这个属性会保存包含响应数据的XML DOM文档
status:响应的http状态
statusText:http状态的说明
响应后,首先检查status属性,http状态200表示成功,此时responseText属性的内容就绪,内容类型正确的同时,responseXML也可以访问了
状态304表示请求的资源没有被修改,可以直接使用浏览器中缓存的版本,
xhr.open("get","example.text",false);
xhr.send(null);
if((xhr.status >= 200 && xhr.status <= 300) || xhr.status == 304){
alert(xhr.responseText);
}else{
alert("Request was unsuccessful:" + xhr.status);
}
一般情况下使用异步请求,此时js会继续执行不必等待服务器的响应,此时检测XHR的readyState属性,属性值如下
0:未初始化,尚未调用open方法
1:启动,调用了open方法,未调用send方法
2,发送,调用了send方法,未接受到响应
3,接收,接收到部分响应数据
4,完成,接收到全部的响应数据,而且在客户端使用了
只要readyState属性的值发生变化就会触发readystatechange事件,可以利用这个事件检测每次状态后的readyState的值
必须在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",true); xhr.send(null);
响应之前调用xhr.abort(),可以取消异步请求,由于内存原因,不建议重用XHR对象
2,HTTP头部信息
每个http请求和响应都会带有相应的头部信息,XHR对象也提供了操作这两种头部信息的方式
发送XHR请求的同时,会发送下列头部信息
Accept:浏览器能够处理的内容类型
Accept-Charset:浏览器能够显示的字符集
Accept-Encoding:浏览器能够处理的压缩编码
Accept-Language:浏览器当前设置的语言
Connection:浏览器与服务器之间连接的类型
Cookie:当前页面设置的任何cookie
Host:发送请求的页面所在的域
Referer:发送请求的页面的URL
User-Agent:浏览器的用户代理字符串
setRequestHeader(头部字段的名称,头部字段的值)方法可以自定义设置头部信息,在open和send方法之前调用此方法
var xhr = createXHR(); xhr.onreadystatechange = function(){ if(xhr.readyState == 4){ if((xhr.status >= 200 && xhr.status <= 300) || xhr.status == 304){ alert(xhr.reponseText); }else{ alert("Request was unsuccessful :" + xhr.status); } } }; xhr.open("get","example.php",true); xhr.setRequestHeader("MyHeader","MyValue"); xhr.send(null);
调用XHR对象的getResponseHeader()方法,传人头部字段名称,可以返回相应的响应头部信息
getAllResponseHeaders()方法,取得一个包含所有头部信息的长字符串
var myheader = xhr.getResponseHeader("myHeader");
var allheader = xhr.getAllResponseHeader();
服务器端,可以利用头部信息向浏览器发送额外的,结构化的数据,没有自定义的情况下,getAllResponseHeader()方法会返回如下
Date:sun,14 Nov 2004 18:04:03 GMT
Server: Apache/1.3.29(Unix)
Vary:Accept
X-Powered-By:PHP/4.3.8
Connection:close
Content-Type:text/html;charset=iso-8859-1
3,GET请求
用于向服务器查询某些信息,可以将查询字符串追加到URL末尾,
查询字符串,每个参数的名称和值使用encodeURIComponent()进行编码,而且所有名值对使用&分隔
xhr.open("get","example.php?name1=value&name2=value2",true);
使用下面函数向现有的URL末尾添加查询字符串参数
function addURLParam(url,name,value){
url += (url.indexof("?") == -1 ? "?" : &);
url += encodeURIComponent(name) + "=" + encodeURIComponent(value);
return url;
}
使用下面函数来构建请求URL示例
var url = "example.php";
url = addURLParam(url,"name","Nicholas");
xhr.open("get",url,true);
4,POST请求
用于向服务器发送应该被保存的数据,
post请求将数据作为请求的主体提交,请求的主体可以包含非常多的数据,格式不限
发送post请求,要向send()方法中传人某些数据,可以是XML DOM文档,可以使字符串
XHR模仿表单提交,首先将Content-type头部信息设置为application/x-www-form-urlencoded,然后以适当的格式创建一个字符串,可以使用serialize() 函数来创建字符串
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));
}
php文件通过$_POST取得提交的数据
<?php
header("Content-type:text/plain");
echo <<<EOF
Name:{$_POST["user-name"]}
Email:{$_POST['user-email']}
EOF;
?>
如果不设置Content-type头部信息,那么发送给服务器的数据不会出现在$_POST超级全局变量中,
要访问同样的数据,使用$HTTP_RAW_POST_DATA
二,XMLHttpRequest2级
1,FormData
为了实现表单数据的序列化,FormData类型为序列化表单以及创建与表单格式相同的数据
var data = new FormData();
data.append("name","Nicholas");
append()方法接收两个参数,键值对,
通过向FormData构造函数中传人表单元素,可以用表单元素的数据预先向其中填入键值对
var data = new FormData(document.forms[0]);
创建了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));
XHR对象能够识别传人的数据类型是FormData的实例,并配置适当的头部信息
2,超时设定
XHR对象的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.timoeout = 1000; xhr.ontimeout = function(){ alert("Request did not return in a second."); }; xhr.send(null);
3,overrideMimeType()方法
Firefox最早引入了overrideMimeType()方法,用于重写XHR响应的MIME类型,可以把响应当做XML而非纯文本来处理
var xhr = createXHR();
xhr.open("get","text.php",true);
xhr.overrideMimeType("text/XML");
xhr.send(null);
三,进度事件
Progress Events规范定义了与客户端服务器通信有关的事件
loadstart:在接收到响应数据的第一个字节时触发
progress:在接收响应期间持续不断的触发
error:在请求发生错误时触发
abort:因为调用abort()方法而终止连接时触发
load:在接收到完整的响应数据时触发
loadend:在通信完成,或者触发了error,abort,load事件后触发(尚未被任何浏览器支持)
1,load事件
firefox引入了load事件,用于替代readystatechange事件
响应接收完毕后触发load事件,没有必要去检测readyState属性了,
onload会接收到event对象,target属性就指向XHR对象,可以访问到XHR对象的所有方法和属性
对于不支持的浏览器
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","alertEvents.php",true); xhr.send(null);
2,progress事件
事件在浏览器接收数据期间周期性触发,
onprogress事件处理程序会接收到一个evnet对象,其target属性是XHR对象
包含额外的三个属性:lengthComputable(进度信息是否可用的布尔值),position(已经接收的字符数),totalSize(根据Content-Length响应头部确定的预期字节数)
var xhr = createXHR(); xhr.onload = function(){ if((xhr.status >= 200 && xhr.status <= 300) || xhr.status == 304){ alert(xhr.responsText); }else{ alert("Request was unsuccessful" + xhr.status); } }; xhr.onprogress = function(event){ var divStatus = document.getElementById("status"); if(event.lengthComputable){ divStatus.innerHTML = "Recevied " + event.position + "Of" + event.totalSize + "bytes"; } }; xhr.open("get","alertEvent.php",true); xhr.send(null);
四,跨源资源共享(CORS)
思想是使用自定义的http头部,让浏览器与服务器进行沟通,从而决定请求或响应的成功与否
请求的origin头部,包含请求页面的源信息(协议,域名,端口),
如果服务器认为是可接受的,在Origin-Control-Allow-Origin头部中回发相同的源信息,如果是公共资源,可以回发*
1,IE对CORS的实现
IE8引入XDR类型,类似XHR对象,但能实现安全可靠的跨域通信,区别如下
cookie不会随请求发送,也不会随响应返回
只能设置请求头部信息中的Content-type字段
不能访问响应头部信息
只支持GET和POST请求
XDR使用方法,创建一个XDomainRequest的实例,调用open()方法,再调用send()方法,
open()方法只接收两个参数,请求类型的UR,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);
接收到响应后,只能访问响应的原始文本,没有办法确定响应的状态代码,只要响应有效就会会触发load事件,
失败就会触发error事件,遗憾的是,除了错误本身外,没有其他信息可用,因此唯一能够确定的就是请求未成功,
检测错误可以指定一个onerror事件处理程序
var xdr = new XDomainRequest();
xdr.onload = function(){alert(xdr.responseText)};
xdr.onerror = function(){alert("An error occurred.")};
xdr.open("get","http://www.somewhere-else.com/page/");
xdr.send(null);
返回请求前调用abrot()方法会终止请求
同样支持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对象提供了contenType属性,用来表示发送数据的格式
var xdr = new XDomainRequest();
xdr.onload = function(){};
xdr.onerror = function(){};
xdr.open("post","http://www.somewhere-else.com/page/");
xdr.contentType = "application/x-www-form-urlencoded";
xdr.send("name1=value1&name2=value2");
2,其他浏览器对CORS的实现
通过XMLHttpRequest对象实现对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","htt://www.somewhere-else.com/page/",true); xhr.send(null);
跨域XHR对象有限制,
不能使用setRequestHeader()设置自定义头部
不能发送和接受cookie
调用getAllResponseHeader()方法总会返回空字符串
3,Preflighted Request
透明服务器验证机制支持开发人员使用自定义的头部,
使用下列高级选项来发送请求时,就会向服务器发送一个Preflight请求,使用OPTIONS方法,发送下列头部
Origin:与简单的请求相同
Access-Control-Request-Method:请求自身使用的方法
Access-Control-Request-Header:自定义的头部信息,多个头部用逗号分隔
Origin:http://www.nczonline.net
Access-Control-Request-Method:POST
Access-Control-Request-Header:NCZ
发送这个请求后,服务器可以决定是否允许这种类型的请求,服务器通过在响应中发送如下头部与浏览器进行沟通
Access-Control-Allow-Origin:与简单请求相同
Access-Control-Allow-Method:允许的方法,多个方法以逗号分隔
Access-Control-Allow-Header:允许的头部,多个头部以逗号分隔
Access-Control-Max-Age:应该将这个Prefight请求缓存多长时间(秒)
Access-Control-Allow-Origin:http://www.nczonline.net
Access-Control-Allow-Method:POST,GET
Access-COntrol-Allow-Header:NZC
Access-Control-Max-Age:1728000
第一次发送这种请求时会多一个http请求
4,带凭据的请求
默认情况下跨源请求不会提供凭据(cookie,HTTP认证,客户端SSL证明)
通过将withCredentials属性设置为true,可以指定某个请求应该发送凭据
如果服务器端接收带凭据的请求,会使用Access-Control-Allow-Credentials:true来响应
如果服务器的响应中没有这个头部,浏览器就不会把响应交给javascript,responseText中将是空字符串status的值为0,调用onerror事件处理程序
5,跨浏览器的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.send(); }
firefox,safari,chrome中的XMLHttpRequest对象和IE中XDomainRequest对象类似,共有属性方法如下
abort(),用于停止正在进行的请求
onerror(),用于替代onreadystatechange()检测错误
onload(),用于替代onreadystatechange()检测成功
responseText(),用于取得响应内容
send(),用于发送请求
五,其他跨域技术
1,图像Ping
使用img标签,动态的创建图像,使用onload和onerror事件处理程序来确定是否接收到了响应
图形Ping是与服务器进行简单,单向,的跨域通信的一种方式
请求的数据通过查询字符串形式发送的,响应的是任意内容,通常是像素图或204响应
var img = new Image();
img.onload = img.onerror = function(){};
img.src = "http://www.example.com/test?name=Nicholas";
请求从设置src属性那一刻开始,请求中发送了name参数
图像Ping用于跟踪用户点击页面或动态广告曝光次数,
两个缺点,只能发送get请求,无法访问服务器的响应文本,只能单向通信
2,JSONP
被包含在函数调用中的JSON,
callback({"name":"Nicholas"});
JSONP包含两部分,回调函数和数据,回调函数是当响应到来时应该在页面中调用的函数,函数名一般在请求中指定
数据就是传人回调函数中的JSON数据,例如:http://freegeoip.net/json/?callback=handleResponse,指定的函数名为handleResponse()
JSONP是通过动态script元素使用的,使用时可以为src属性指定一个跨域URL
function handleResponse(response){
alert("you're at IP address" + resopnse.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);
可以直接访问响应文本,支持在浏览器与服务器之间双向通信,但不能保证安全,确定JSONP请求是否失败不是很容易
html5给script提供onerror事件处理程序,尚未得到任何任何浏览器支持,为此常通过设置计时器检测指定时间内是否收到了响应
3,Comet
Ajax是一种从页面向服务器请求数据的技术,Comet是一种服务器向页面推送数据的技术
Comet能够让信息近乎实时的被推送到页面上,非常适合处理体育比赛的分数和股票报价
两种实现Comet的方式:长轮询和流
长轮询:页面发起一个到服务器的请求,然后服务器一直保持连接打开,直到有数据可发送,
发送完数据之后,浏览器关闭连接,随即又发起一个到服务器的新请求,这一过程在页面打开期间持续不断
长轮询是服务器等待发送响应后发送数据
HTTP流:浏览器向服务器发送一个请求,服务器保持连接打开,然后周期性向浏览器发送数据
DOM浏览器中,通过监听readystatechange事件及检测readyState的值是否为3,利用XHR对象实现流
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!"); });
4,服务器发送事件
1)SSE API
创建到服务器的单向连接,服务器通过这个连接可以发送任意数量的数据,
服务器响应的MIME类型必须是text/event-stream,支持长短轮询,和http流
首先创建一个新的EventSource对象,并传进一个入口点:
var source = new EventSource("Myevent.php");url要与创建对象的页面同域
EventSource实例有个readyState属性,0表示正连接到服务器,1表示打开了连接,2表示关闭了连接
还有open事件,在建立连接时触发,message事件,在服务器接收到新事件时触发,error事件,在无法建立连接时触发
服务器发回的数据以字符串形式保存在event.data中
默认情况下,EventSource对象会保持与服务器的活动连接,连接断开会重新连接
强制断开使用close()方法,source.close()
2)事件流
服务器事件会通过一个持久的http响应发送,这个响应的MIME类型为text/event-stream
响应的格式为纯文本,最简单的情况是每个数据项前有data
对于多个连续的以data开头的数据行,将作为多段数据解析,每个值之间有一个换行分隔符
只有包含data:的数据行后有空行,才会触发messge事件,
通过id:前缀可以给特定的事件指定一个关联的ID,位于data:行前面和后面皆可
设置了ID,EventSource对象会跟踪上一次触发的事件,连接断开时,会向服务器发送一个包含名为last-Event-ID的特殊HTTP头部的请求,以便服务器知道 下一次触发的事件
5,Web Socket
在一个单独的持久的连接上提供全双工,双向通信
一个http请求发送到服务器已发起连接,取得服务器响应后,建立的连接使用http升级从http协议交换为web Socket协议
ws://;wss://
1)Web Socket API
首先实例一个WebSocket对象并传人要连接的URL
var socket = new WebSocket("ws://www.example.com/sever.php");
必须给websocket构造函数传人绝对的URL,
浏览器在实例化websocket对象后,马上尝试创建连接,readyState属性
WebSocket.OPENING(0):正在建立连接
WebSocket.OPEN(1):已经建立连接
WebSocket.CLOSING(2):正在关闭连接
WebSocket.CLOSE(3):已经关闭连接
没有readystatechange事件,readyState永远是从0开始的
关闭WebSocket,使用close方法
2)发送和接收数据
send()方法
var socke = new WebSocket("ws://www.example.com/server.php");
socket.send("hello world");
websocket只能发送纯文本数据,对于复杂的结构,将数据序列化为JSON字符串,使用stringify()方法
当服务器向客户端发来消息时,WebSocket对象会触发message事件,这个message事件与其他传递消息的协议类似,
也是把返回的数据保存在event.data属性中
socket.onmessage = function(event){
var data = event.data;
//处理数据
}
3)其他事件
open:在成功建立连接时触发
error:在发生错误时触发,连接不能持续
close:在连接关闭时触发
WebSocket对象不支持DOM2级事件监听器,必须使用DOM0级语法分别定义每个事件处理程序
var socket = new WebSocket("ws://www.example.com/server.php");
socket.onopen = function(){};
socket.onerror = function(){};
socket.onclose = function(){};
只有close事件对象包含额外的三个属性
wasClean:布尔值,表示连接是否已经明确的关闭
code:服务器返回的数据状态码
reason:字符串包含服务器发回的消息
6,SSE与Web Socket
SSE,支持单向的从服务器读取数据
WebSocket,支持双向通信
SEE和XHR组合也可以实现双向通信
六,安全
确保XHR访问的URl安全,通行的做法是验证发送请求者是否有权限访问相应的资源
要求以SSL连接来访问可以通过XHR请求的资源
要求每一次请求都附带经过相应算法计算得到的验证码
要求发送POST而不是GET请求(对CSRF攻击没有作用)
检查来源URL以确保是否可信,(对CSRF攻击没有作用,来源记录很容易伪造)
基于cookie信息进行验证(对CSRF攻击没有作用,容易被伪造)