剖析XMLHttpRequest
学过Ajax的都知道,Ajax与服务器异步交互的核心便是XMLHttpRequest,有了XMLHttpRequest才使的Ajax有了与后交互的能力,今天就来全面的回顾下XMLHttpRequest(ajax的其他组成元素:DOM,Javascript,css等这里就不介绍了)可以看http://www.cnblogs.com/shenliang123/archive/2012/04/25/2470244.html
1.首先介绍XMLHttpRequest对象的方法:
(1)abort---------------------------------停止发送当前请求
(2)getAllResponseHeaders---------------获得服务器返回的所有响应头
(3)getResponseHeader("headerLabel")---根据响应头的名字来获取相应的响应头
(4)open("method", "URL"[,asyncFlag[,"userName",[,"password"]]])
------------------建立与服务器的URL的连接,并设置请求的的方法,以及是否使用异步请求,如果远程服务需要用户名和密码,则提供相应的用户名和密码
(5)send(content)------------------------发送请求,其中content是请求的参数,如果不需要传递参数就将其设为null,在get方式提交时参数拼接在URL后面的,因此就将content设为null,不过以post方式提交也可以将参数拼接在URL后面的,此时也将content设为null,但也可以将参数放到content中进行传递
(6)setRequestHeader("label","value")----该方法一般是在post方式提交的时候用到的,post提交请求前需要先设置请求头
2.无论是何种请求,使用XMLHttpRequest进行连接都应该按照如下步骤:
(1)初始化XMLHttpRequest对象,需要根据不同的浏览器进行不同的创建,因此首先需要判断浏览器的类别
(2)打开与服务器的连接(使用open("method", "URL"[,asyncFlag[,"userName",[,"password"]]]))。打开连接时,指定发送请求的方法:采用get或post;指定是否以异步方式(true为采用异步)
(3)设定监听XMLHttpRequest状态改变的事件处理函数(即设定的回调函数)
(4)发送请求(使用send(content))
3.XMLHttpRequest对象常用的属性:
(1)onreadystatechange-------------------用于指定XMLHttpRequest对象状态改变时的事件处理函数。onreadystatechange属性的作用与按钮对象的onclick属性一样,
它们都是事件处理属性。即XMLHttpRequest是事件源,它可以引发readystatechange事件,当程序将一个函数引用赋给XMLHttpRequest对象的readystatechange属性,
如:objXMLHttp.onreadystatechange = processResponse;processResponse函数即成为XMLHttpRequest对象的事件处理器,每次XMLHttpRequest对象的状态
改变都会触发监听该事件的事件处理器,因此我们需要在事件处理器即函数中进行正当的判断来实现,具体的操作见代码
XMLHttpRequest对象的几种状态:
---> 0 ---------------------XMLHttpRequest对象还没有完成初始化
---> 1 ---------------------XMLHttpRequest对象开始发送请求
---> 2 ---------------------XMLHttpRequest对象的请求发送完成
---> 3 ---------------------XMLHttpRequest对象开始读取服务器的响应
---> 4 ---------------------XMLHttpRequest对象读取服务器响应结束
以上的状态就是通过下面的readyState属性来进行读取的
(2)readyState----------------------------XMLHttpRequest对象的处理状态
(3)responseText-------------------------用于获取服务器的响应文本
(4)responseXML-------------------------用于获取服务器端响应的XML文档对象
(5)status--------------------------------该属性是服务器返回的状态文本信息,只有当服务器的响应已经完成(即readyState==4),才会有这个状态码
服务器常用的状态码和对应的含义如下:
---> 200 -------------------服务器响应正常
---> 304 -------------------该资源在上次请求之后没有任何修改,这个通常用于浏览器的缓存机制,我们为了在请求时放在读取缓存一般会在url地址上拼接上一个时间戳来骗过浏览器
---> 400 -------------------无法找到请求的资源
---> 401 -------------------访问资源的权限不足
---> 403 -------------------没有权限访问资源
---> 404 -------------------需要访问的资源不存在
---> 405 -------------------需要访问的资源被禁止
---> 407 -------------------访问的资源需要代理身份验证
---> 414 -------------------请求的url太长
---> 500 -------------------服务器内部错误
(6)statusText----------------------------该属性是服务器返回的状态文本信息(与status对应),只有当服务器的响应已经完成,才会有这个状态文本信息
下面演示完整的ajax交互
页面:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <script type="text/javascript" src = "js/XMLHttpRequestTest.js"></script> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <title>AjaxTest.html</title> <body> <input type = "text" name = "content" id = "content" > <input type = "button" name = "get" value = "GET发送" onclick = "getSend()"> <input type = "button" name = "post" value = "POST发送" onclick = "postSend()"> </body> </html>
AJAX:
var objXMLHttp; /** * 进行createXMLHttpRequest对象的创建,由于不同的浏览器厂商对于XMLHttpRequest的支持不一样,因此创建的时候需要根据不同的浏览器进行创建 * */ function createXMLHttpRequest(){ //对于Firefox,Opera等遵守DOM 2规范的浏览器 if(window.XMLHttpRequest){ objXMLHttp = new XMLHttpRequest(); } //对于IE浏览器 else{ //将IE浏览器不同的XMLHttp实现声明为数组 var MSXML = ['MSXML2.XMLHTTP.5.0', 'MSXML2.XMLHTTP.4.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP']; //依次对每个XMLHttp创建XMLHttpRequest对象 for(var i = 0; n< MSXML.length; i++){ try{ //微软发布的是ActiveX控件 objXMLHttp = new ActiveXObject(MSXML[i]); //如果正常创建XMLHttpRequest对象就使用break跳出循环 break; }catch(e){ alert("创建XMLHttpRequest对象失败"); } } } } /** * 通过post方式提交 * */ function postSend(){ var value = document.getElementById("content").value; alert(value); //初始化XMLHttpRequest对象 createXMLHttpRequest(); //创建请求的URL var url = "ajaxServlet" //打开与服务器的连接,使用post方式 objXMLHttp.open("POST", url, true); //post方式需要设置请求消息头 objXMLHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); //设置处理响应的回调函数 objXMLHttp.onreadystatechange = processResponse; //发送请求并设置参数,参数的设置为param=value的形式 objXMLHttp.send("value="+value); } /** * 通过GET请求 * */ function getSend(){ var value = document.getElementById("content").value; //alert(value); //初始化XMLHttpRequest对象 createXMLHttpRequest(); alert("创建成功"); //创建请求的URL,get方式采用url拼接参数 var url = "ajaxServlet?value="+value; objXMLHttp.open("GET", url, true); //设置处理响应的回调函数 objXMLHttp.onreadystatechange = processResponse; objXMLHttp.send(null); } /** * 设定的回调函数 * */ function processResponse(){ //响应完成且响应正常 if(objXMLHttp.readyState == 1){ alert("XMLHttpRequest对象开始发送请求"); }else if(objXMLHttp.readyState == 2){ alert("XMLHttpRequest对象的请求发送完成"); }else if(objXMLHttp.readyState == 3){ alert("XMLHttpRequest对象开始读取服务器的响应"); }else if(objXMLHttp.readyState == 4){ alert("XMLHttpRequest对象读取服务器响应结束"); if(objXMLHttp.status == 200){ //信息已经成功返回,开始处理信息 //先捕获下所有的请求头 var headers = objXMLHttp.getAllResponseHeaders(); alert("所有的请求头= "+headers); //得到服务器返回的信息 var infor = objXMLHttp.responseText; alert("服务器端的响应 = "+infor); }else{ alert("所请求的服务器端出了问题"); } } }
服务器端:
package xidian.sl.ajax; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class AjaxServlet extends HttpServlet { private static final long serialVersionUID = 1L; public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); request.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8"); PrintWriter out = response.getWriter(); String value = request.getParameter("value"); System.out.println("value"+ value); out.print("得到的value为 = "+ value); } }
发现上面使用get方式进行提交时,如果输入中文传到服务器端就出现乱码:
输入:
然后点击GET发送,然后查看服务器端打印到控制台的value:
原因:当使用GET方式发送请求时,请求的参数是拼接在url地址后面的,而根据http的传输方式,如果传输的参数为中文,就会编码成url的格式进行传递,此时我们就要对其做点处理了:
(1).在服务器端进行编码格式的转变:先将参数按ISO-8859-1字符集编码成字节数组,然后按UTF-8字符集将该字节数组解码为字符串:
String param = new String(请求参数.getBytes("ISO-8859-1"), "UTF-8"),但这种方式并不是全能的,因为我们这里是将所有的参数都以UTF-8进行解码,但不同的浏览器请求参数的编码格式是不一样的,不一定为UTF-8,如IE默认的编码格式为GBK,需要改成new String(请求参数.getBytes("ISO-8859-1"), "GBK"),因此我们一般选择第二种方式或第三种方式
(2).页面端发出的数据做一次encodeURI,服务器端使用 new String(请求参数.getBytes("iso8859-1"),"utf-8")如:
var url= "AJAXServer?name="+encodeURI($("#userName").val() ) ;
(3)页面端发出的数据做两次encodeURI处理, 服务器端用URLDecoder.decode(请求参数,"utf-8");具体见:http://www.cnblogs.com/shenliang123/archive/2012/04/19/2456758.html
在POST请求时Ajax应用默认采用UTF-8字符集来编码请求参数,在服务器端可以不使用request.setCharacterEncoding("UTF-8");
但养成良好的习惯还是建议写request.setCharacterEncoding("UTF-8");
还有一个问题就是为了不让浏览器读取缓存,我们需要在url地址后拼接一个时间戳来骗过浏览器:
//给URL增加时间戳,骗过浏览器,不读取缓存 function convertURL(url){ //获取时间戳 var timstamp=(new Date()).valueOf(); //将时间戳信息拼接到URL上 if(url.indexOf("?")>=0){//用indexof判断该URL地址是否有问号 url=url+"&t="+timstamp; }else{ url=url+"?t="+timstamp; } return url; }
详细见:http://www.cnblogs.com/shenliang123/archive/2012/04/19/2456758.html
从返回的结果可以看出:返回的所有响应头并不是组成一个数组,而是由“名:值”组成的键值对字符串
到此我们对于XMLHttpRequest了解的差不多了,但我们上面介绍的都是文本的请求,在回调函数中是通过responseText属性来获取服务器端返回的文本,下面我们要介绍发送
xml请求,这个适合于发送复杂的参数到服务器端,我们可以将客户端页面的参数封装为xml字符串形式:
在上面的js中添加两个方法:
/** * 创建xml文档 * */ function createXML(){ //开始创建XML文档,countrys是根元素 var xml = "<countrys>" //获取country元素,并获取其所有的子元素 var options = document.getElementById("country").childNodes; var option = null; //遍历城市下拉列表的所有选项 for(var i = 0; i< options.length; i++){ option = options[i]; //判断是否被选中 if(option.selected){ //在countrys节点下增加一个country子节点,这里需要对 / 进行转义 xml = xml+"<country>"+option.value+"<\/country>"; } } //结束xml根节点 xml = xml+"<\/countrys>"; //返回 return xml; } /** * 使用xml进行传递 * */ function send(){ //初始化XMLHttpRequest对象 createXMLHttpRequest(); //创建请求的URL var url = "xmlServlet" //打开与服务器的连接,使用post方式 objXMLHttp.open("POST", url, true); //post方式需要设置请求消息头 objXMLHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); //设置处理响应的回调函数 objXMLHttp.onreadystatechange = processResponseXML; //发送xml请求,此时参数的设置不再是param=value的形式进行发送,而是直接采用xml字符串作为参数 objXMLHttp.send(createXML()); } /** * xml请求的回调函数 * */ function processResponseXML(){ //响应完成且响应正常 if(objXMLHttp.readyState == 1){ alert("XMLHttpRequest对象开始发送请求"); }else if(objXMLHttp.readyState == 2){ alert("XMLHttpRequest对象的请求发送完成"); }else if(objXMLHttp.readyState == 3){ alert("XMLHttpRequest对象开始读取服务器的响应"); }else if(objXMLHttp.readyState == 4){ alert("XMLHttpRequest对象读取服务器响应结束"); if(objXMLHttp.status == 200){ //信息已经成功返回,开始处理信息 //先捕获下所有的请求头 var headers = objXMLHttp.getAllResponseHeaders(); alert("所有的请求头= "+headers); //得到服务器XML相应,这里是通过responseXML属性来获得,这也是唯一区别的地方 var infor = objXMLHttp.responseXML; alert("服务器端的响应 = "+infor); }else{ alert("所请求的服务器端出了问题"); } } }
页面端:就是一个可以多选的下拉框
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <title>XMLAjaxTestl.html</title> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <script type="text/javascript" src = "js/XMLHttpRequestTest.js"></script> </head> <body> <!-- 添加 multiple = "multiple"属性后下拉框可以为多选--> <select name = "country" id = "country" multiple = "multiple"> <option value = "1" selected = selected>浙江</option> <option value = "2">北京</option> <option value = "3">上海</option> </select> <input type = "button" name = "send" value = "发送" onclick = "send()"/> </body> </html>
服务器端的处理:
package xidian.sl.ajax; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.util.Iterator; import java.util.List; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.XPPReader; public class XMLServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); StringBuffer buffer = new StringBuffer(); String line = null; //通过request获取输入流 BufferedReader reader = request.getReader(); //依次读取请求输入流中的数据 while((line = reader.readLine())!=null){ buffer.append(line); } //将从输入流中读取到的数据转化为字符串 String xml = buffer.toString(); InputStream is = new ByteArrayInputStream(xml.getBytes()); //采用dom4j解析xml字符串.read(new ByteArrayInputStream(xml.getBytes())); Document xmldoc = new XPPReader().read(is); //获取countrys节点的所有字节点 List countryList = xmldoc.getRootElement().elements(); //定义服务器的响应结果 String result = ""; for(Iterator iterator = countryList.iterator();iterator.hasNext();){ org.dom4j.Element country = (Element)iterator.next(); if(country.getText().equals("1")){ result += "杭州"; }else if(country.getText().equals("2")){ result += "海淀"; }else if(country.getText().equals("3")){ result += "明珠"; } } out.print(result); } }
由于发送到服务器端的是一个xml字符串,因此服务器端不能直接通过request.getParameter();来得到请求参数,而是必须以流的形式来获取请求参数;
下面将给出一个通用的并且是使用池的技术管理的XMLHttpRequest,因为对于大型的js应用,XMLHttpRequest的使用时很频繁的,因此使用缓存会更加的高效:
var XMLHttp = { //定义第一个属性,该属性用于缓存XMLHttpRequest XMLHttpRequestPool:[], //对象的第一个方法用于返回一个XMLHttpRequest对象 getInstance:function(){ //从XMLHttpRequest对象池中取出一个空闲的XMLHttpRequest对象 for(var i = 0; i< XMLHttpRequestPool.length; i++){ //判断XMLHttpRequest对象是否为空闲,只需要判断readyState就可以了,如果readyState为0或4就表示当前XMLHttpRequest对象为空闲 if(this.XMLHttpRequestPool[i].readyState == 0|| this.XMLHttpRequestPool[i].readyState == 4){ return this.XMLHttpRequestPool[i]; } } //如果没有空闲的就只能再次创建一个新的XMLHttpRequest对象 this.XMLHttpRequestPool[XMLHttpRequestPool.length] = this.createXMLHttpRequest(); //返回刚刚创建的XMLHttpRequest对象 return this.XMLHttpRequestPool[XMLHttpRequestPool.length-1]; }, //创建新的XMLHttpRequest对象 createXMLHttpRequest:function(){ //对于Firefox,Opera等遵守DOM 2规范的浏览器 if(window.XMLHttpRequest){ var objXMLHttp = new XMLHttpRequest(); } //对于IE浏览器 else{ //将IE浏览器不同的XMLHttp实现声明为数组 var MSXML = ['MSXML2.XMLHTTP.5.0', 'MSXML2.XMLHTTP.4.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP']; //依次对每个XMLHttp创建XMLHttpRequest对象 for(var i = 0; n< MSXML.length; i++){ try{ //微软发布的是ActiveX控件 var objXMLHttp = new ActiveXObject(MSXML[i]); //如果正常创建XMLHttpRequest对象就使用break跳出循环 break; }catch(e){ alert("创建XMLHttpRequest对象失败"); } } } //Mozilla的某些版本没有readyState属性 if(objXMLHttp.readyState == null){ //直接设置为0 objXMLHttp.readyState = 0; //对于那些没有readyState属性的浏览器,将load动作与与下面函数相关联 objXMLHttp.addEventListener("load", function(){ //当从服务器上加载完数据后,将readyState属性设为4 objXMLHttp.readyState = 4; if(typeof objXMLHttp.onreadystatechange == "function"){ objXMLHttp.onreadystatechange(); } },false); } return objXMLHttp; }, //定义对象的第三个方法:发送请求(方法[post:get],地址,数据源,回调函数) sendRequest:function(method, url, data, callback){ //得到XMLHttpRequest对象 var objXMLHttp = this.getInstance(); with(objXMLHttp){ try{ //增加一个额外的请求参数,用于防止IE读取服务器缓存 uri = convertURL(url); //打开与服务器的连接 open(method, uri, true); //对于使用post提交的 if(method == "POST"){ //设定消息请求头 setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); send(data); } //对于用get方式提交的 if(method == "GET"){ send(null); } //设置状态改变的回调函数 onreadystatechange = function(){ //当服务器响应结束并得到了正常的服务器响应 if(objXMLHttp.readyState == 4&& objXMLHttp.status == 200){ //调用回调函数 callback(objXMLHttp); } } }catch(e){ alert(e) } } }, convertURL:function (url){ //获取时间戳 var timstamp=(new Date()).valueOf(); //将时间戳信息拼接到URL上 if(url.indexOf("?")>=0){//用indexof判断该URL地址是否有问号 url=url+"&t="+timstamp; }else{ url=url+"?t="+timstamp; } return url; } }
上面的池的实现就是简单的使用一个数组来存储已存在的XMLHttpRequest对象,这样这个数组就成了一个XMLHttpRequest对象池,实现缓存的作用,每次发送请求只要从对象池中取出一个闲置的XMLHttpRequest对象,如果此时不存在闲置的对象就创建一个新的XMLHttpRequest对象
以后我们在使用Ajax的时候只需要将这个js代码进行引入,然后直接调用方法XMLHttp.sendRequest("POST/GET", url, data, callback);
这样是不是有点像jquery对于get方式提交的封装:$.get("AjaxServer?name="+userName,null,callback);
具体实例可以见:http://www.cnblogs.com/shenliang123/archive/2012/04/19/2456735.html