AJAX技术入门 第二节 XMLHttpRequest对象的使用
1.知识补充:div 和 span 差别
div 默认是块级元素;span 默认是行内元素
2. XMLHttpRequest对象 的五步使用法
第一步:创建XMLHttpRequest对象
区别不同的浏览器
•IE“独树一帜”,仍然是以ActiveX组件的方式来创建XMLHttpRequest对象
•其他浏览器则可以直接创建javascript的XMLHttpRequest对象。
//第一步:创建XMLHttpRequest对象 var xml; var resultNode = document.getElementById("result"); if (window.XMLHttpRequest) { xml = new XMLHttpRequest(); if (xml.overrideMimetype) { xml.overrideMimeType("text/xml"); } } else if (window.ActiveXObject) { var MSXML = ['MSXML2.XMLHTTP.6.0', 'MSXML2.XMLHTTP.5.0', 'MSXML2.XMLHTTP.4.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP']; for (var n = 0; n < MSXML.length; n++) { try { xml = new ActiveXObject(MSXML[n]); break; } catch (e) { } } } if(xml == null){ resultNode.innerHTML = "不能建立XMLHttpRequest对象"; return false; }
“window.XMLHttpRequest”为true时表示当前浏览器是IE7或其他浏览器,我们就可以使用new XMLHttpRequest()的方式来创建一个XMLHttpRequest对象
if(xmlhttp.overrideMimetype)这个逻辑的作用是解决部分版本的Mozilla在服务器响应头信息没有XML时不能正常工作的问题。
“window.ActiveXObject”为true时表示当前的浏览器为IE6.0及以下的版本,要使用new ActiveXObject(控件名)的方式来创建一个XMLHttpRequest对象
这里需要注意的是不同版本IE中用于建立XMLHttpRequest对象的控件版本很多
如果使用MSXML数组中的某一个控件名称成功建立了XMLHttpRequest对象,则跳出循环,如果建立失败会有异常抛出,被catch以后继续进行循环,尝试下一个控件名称
这里的控件名称是按照从新到老的顺序排列的,这样可以保证使用较新版本IE的用户可以较早成功建立XMLHttpRequest对象。
如果没有建立成功XMLHttpRequest对象,则不能继续后面与服务器端交互的工作,函数只能返回。
第二步:注册回调方法
希望服务器端的数据返回给浏览器时执行回调方法(注册的是方法名)
//第二步:注册回调方法
xml.onreadystatechange = callback;
然后我们做的工作是设置一个回调函数
回想前面我们说过的AJAX的模式的特点,由于我们采用异步交互的模型,不像同步模式一样可以直接接收响应内容,
因此我们需要告诉AJAX引擎当响应数据回来时我要做一些工作,这些工作就是回调函数中的内容。
这里需要注意的是设置回调函数时应该只给出回调函数的名称,后面不要带括号,
因为带上括号就变成让XMLHttpRequest对象的onreadystatechange属性值等于回调函数的返回值了。
第三步:设置和服务器端交互的相应参数
第四步:设置向服务器发送的数据,启动和服务器端的交互
第三步和第四步的代码:
首先 :name = encodeURI(encodeURI(name));
接着:
//第三步:设置和服务器端交互的相应参数 xml.open("GET", "AJAXServlet?name=" + name, true);//get方式 //第四步:设置向服务器发送的数据,启动和服务器端的交互 xml.send(null); // xml.open("POST","AJAXServlet",true);//post方式的第三部和第四步 // xml.setRequestHeader("Content-Type","application/x-www-form-urlencoded");//这一步很重要 // xml.send("name="+name);
open方法第一个参数表示http连接的方法, 一般我们使用“GET”或“POST”方式,两种方式的区别会在后面阐述。
第二个参数是服务器端地址,由于使用GET方式,因此要传送给服务器端的数据也在URL中,这里我们使用了两个encodeURI,
目的是为了解决URL中的中文信息在服务器端解码的问题,配合服务器端的URLDecoder.decode(old,“UTF-8”)语句可以保证中文信息在服务器端也可以正常被解出。
第三个参数表示是否采用异步方式进行传输,其中true表示采用异步方式,我们在AJAX中看重的就是异步方式,因此这个参数我们通常使用true。
再接着就是向服务器端发送数据,这里由于已经在URL中包含了发送给服务器的数据,因此在send时就不需要参数了,如果是posr方式就不一样了
GET和POST的区别:
“GET”和“POST”的一个重要区别在于,“GET”方式传递给服务器的信息一般以后缀参数方式存在于URL地址中,
而URL的长度通常都有限制,这也就限制了“GET”方式传递给服务器的内容大小
而“POST”方式传递给服务器的信息并不位于URL地址中,所以没有大小限制。
通常我们在服务器端的Servlet中,doGet和doPost做的是一样的工作,因此由于POST传递信息没有大小限制,我们实际应用中比较多的会使用POST。
第五步:编写回调方法,判断和服务器端的交互是否完成,还要判断服务器端是否正确返回了数据
//第五步:判断和服务器端的交互是否完成,还要判断服务器端是否正确返回了数据 function callback(){ if (xml.readyState == 4) { if (xml.status == 200) { resultNode.innerHTML = xml.responseText; } else { resultNode.innerHTML = "服务器故障"; } } }
这里readyState=4时表示服务器端的响应数据已经被全部接收,readyState还有其他状态,后面会进行详细介绍。
Status=200表示http连接状态正常,如果不是200,则表示http连接有误,此时回来的数据也不是我们需要的。
当响应数据全部接收并且http连接状态正确时,我们就可以接收响应的数据了,这里使用了xmlhttp.responseText用于以文本形式接收响应的数据,
当然也可以用XML方式接收,后面会做详细介绍。
每次readyState变化时onreadystatechange属性对应的函数都会被调用。
之前我们曾经说过onreadystatechange属性设置回调函数是为了在接收到响应数据后对响应数据进行处理,
之所以这样说是因为我们通常只关心收到响应数据以后时的工作,也就是readyState=4时的状态,因此我们的回调函数中也用readyState==4来做判断。
需要注意的内容:
1.不同浏览器下XMLHttpRequest对象的不同的建立方式
2.设置回调函数时不要加括号
3. open方法三个参数含义,此外还需要注意GET方式和POST方式服务器端地址的不同写法
4. GET方式和POST方式send的参数的不同之处,以及POST方式下send之前需要设置请求头信息的工作
5.如何判断正确的响应数据已经返回,此外还要注意如何获取响应数据内容。
3.XMLHttpRequest对象的属性和方法的介绍
方法:
abort()
停止当前请求
getAllResponseHeaders()
返回包含HTTP请求的所有响应头信息,其中响应头包括Content-Length,Date,URI等内容。
返回值是一个字符串,包含所有头信息,其中每一个键名和键值用冒号分开,每一组键之间用CR和LF(回车加换行符)来分隔
getResponseHeader(String header)
返回HTTP请求的响应头中指定的键名header对应的值
open(String method,String url,boolean asynch,String username,String password)
建立对服务器的调用。
其中method表示HTTP调用方法。一般使用“GET”,“POST”
url表示调用的服务器的地址
asynch表示是否采用异步方式,true表示异步
后两个参数可以不指定,username和password分别表示用户名和密码,提供http认证机制需要的用户名和密码
send(content)
向服务器发出请求,如果采用异步方式,该方法会立即返回。
Content可以不指定,其内容可以是DOM对象,输入流或是字符串。
setRequestHeader(String header,String value)
设置HTTP请求中的指定首部header的值为value。
此方法需在open方法以后调用。
onreadystatechange
请求状态改变的事件触发器(readyState变化时会调用此方法)。通常是一个javascript函数
属性:
readyState
readyState一改变,回调函数被调用
请求状态
0=未初始化。
1=open方法成功调用以后。
2=服务器已经应答客户端的请求。
3=交互中。Http头信息已经接收,响应数据尚未接收。
4=完成。数据接收完成。
responseText
服务器返回的文本内容
responseXML
服务器返回的兼容DOM的XML内容
status
服务器返回状态码。200表示“成功”,404表示“未找到”, 500表示“内部错误”
statusText
服务器返回状态码的文本信息。
正在接收数据:IE 和 FF 浏览器的区别
状态:
IE:0,1,2,3,4
FF:0,1,1,2,3,4
完整的示例代码:
新建一个Web 项目 ajaxTest,新建html文件XmlHttpRequest.html
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <script type="text/javascript"> var xml; var resultNode; function test(){ //第一步:创建XMLHttpRequest对象 resultNode = document.getElementById("result"); var name = document.getElementById("name").value; if (window.XMLHttpRequest) { xml = new XMLHttpRequest(); if (xml.overrideMimeType) { xml.overrideMimeType("text/xml"); } } else if (window.ActiveXObject) { var MSXML = ['MSXML2.XMLHTTP.6.0', 'MSXML2.XMLHTTP.5.0', 'MSXML2.XMLHTTP.4.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP']; for (var n = 0; n < MSXML.length; n++) { try { xml = new ActiveXObject(MSXML[n]); break; } catch (e) { } } } if (xml == null) { resultNode.innerHTML = "不能建立XMLHttpRequest对象"; return false; } //第二步:注册回调方法 xml.onreadystatechange = callback; //第三步:设置和服务器端交互的相应参数 name = encodeURI(encodeURI(name)); //xml.open("GET", "AJAXServlet?name=" + name, true);//get方式 //第四步:设置向服务器发送的数据,启动和服务器端的交互 //xml.send(null); //xml.send(""); // if (window.XMLHttpRequest) // firefox // { // try { // netscape.security.PrivilegeManager.enablePrivilege("UniversalBrowserRead"); // } // catch (exception) { // alert(exception); // } // } try { xml.open("POST", "AJAXServlet", true);//post方式的第三部和第四步 xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");//这一步很重要 xml.send("name=" + name); } catch (e) { var msg = document.getElementById("msg"); msg.innerHTML = e; } } //第五步:判断和服务器端的交互是否完成,还要判断服务器端是否正确返回了数据 function callback(){ //alert(xml.readyState); if (xml.readyState == 4) { if (xml.status == 200) { resultNode.innerHTML = xml.responseText; } else { resultNode.innerHTML = "服务器故障"; } } } </script> </head> <body> <input type="text" id="name" value=""><input type="button" value="验证用户" onclick="test()"> <br/> <div id="result"></div><br/> <div id="msg"></div> </body> </html>
在 src 目录下新建一个 Servlet ,AJAXServlet 映射的路径是 /AJAXServlet
import java.io.IOException; import java.io.PrintWriter; import java.net.URLDecoder; 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 = 475800438484791392L; public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=utf-8"); PrintWriter out = response.getWriter(); // 1.取参数 String old = request.getParameter("name"); // String name = new String(old.getBytes("iso8859-1"),"UTF-8"); String name = URLDecoder.decode(old, "UTF-8"); // 2.检查参数是否有问题 if (old == null || old.length() == 0) { out.println("用户名不能为空"); } else { // String name = URLDecoder.decode(old,"UTF-8"); // byte[] by = old.getBytes("ISO8859-1"); // String name = new String(by,"utf-8"); // String name = URLDecoder.decode(old,"utf-8"); // 3.校验操作 if (name.equals("wangxingkui")) { // 4.和传统应用不同之处。这一步需要将用户感兴趣的数据返回给页面段,而不是将一个新的页面发送给用户 // 写法没有变化,本质发生了改变 out.println("用户名[" + name + "]已经存在,请使用其他用户名"); } else { out.println("用户名[" + name + "]尚未存在,可以使用该用户名注册"); } } out.close(); } public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doGet(request, response); } }
部署项目,启动Tomcat,打开FF进行测试
结果视图:
再点击“验证用户”之后,应该还会弹出一个对话框,点击 “是”就行
注意:不能是使用html地址去访问!否则会报错!Access to restricted URI denied !
4. 解决HTTP请求的缓存问题
使用时间戳的思想:
给url地址最后加上时间戳,这样在浏览器看来每次请求的url地址都不一样,因此就不会出现缓存问题,我们一般采用如下方式。
其中url是原始请求地址,代码中根据原url中是否已有参数信息来进行时间戳参数的增加。
万能代码:
//给url地址增加时间戳,骗过浏览器,不读取缓存 function convertURL(url) { //获取时间戳 var timstamp = (new Date()).valueOf(); //将时间戳信息拼接到url上 //url = "AJAXServer" if (url.indexOf("?") >= 0) { url = url + "&t=" + timstamp; } else { url = url + "?t=" + timstamp; } return url; }
时间戳的用处很多,还有一个很重要的应用是显示和更换验证码
5.解决AJAX应用中的中文乱码问题
一般情况下都是保持页面端和http响应头的charset是一致的!
IE6的特别之处:如果都是gb2312还是会出现中文乱码,但是将http头改成utf-8却可以了!
还有一种解决方法是使用只两个ActiveX名称来创建XMLHttpRequest对象
解决请求数据乱码的问题:
①页面端两次 encode
②服务端一次 unencode
过程:“中”字第一次encode:首先经过了utf-8的编码变成了字节码[-28,-72,-83],然后变成16进制就是 E4,B8,AD,最后加上%,变成了 %E4%B8%AD
第二次encode:%变成%25,%25E4%25B8%25AD
然后传给了Servlet,但是在Servlet处理之前,首先会经过应用服务器(例如Tomcat)处理一次,这里会进行一次decode,%25变成%,%E4%B8%AD
最后,在Servlet处理时,再执行一次decode,并且是和“中”字第一次encode时一样的编码格式 utf-8!这样,经过一个逆过程就可以还原出“中”字!
6.解决AJAX应用中的跨域问题
访问远程服务器
•IE6:访问跨域页面时会给出提示,用户确认后会访问
•IE7,Mozilla FireFox及其他:不允许访问跨域页面
解决方法:使用代理 Proxy
在浏览器端的代码中,我们需要在调用open方法之前判断一下要连接的地址是不是以http开头的,如果是则认为要访问的是跨域的资源,
首先将当前url中的”?”变成”&”,这是因为将要连接的地址改为”Proxy?url=” + url以后,如果原来url地址中有参数的话,
新的url地址中就会有两个“?”,这会导致服务器端解析参数错误,”url=”之后的内容表示本来要访问的跨域资源的地址。
function convertURL(url){ if(url.substring(0,7) == "http://"){ url = url.replace("?","&"); url = "Proxy?url=" + url; } return url; }
Proxy:注意Proxy中针对GET方式和POST方式进行了分别的处理,其中GET方式仍然将参数信息拼到URL中,
而POST方式则向HttpURLConnection的数据流中添加参数信息。
由于对本来请求的地址和其包含的参数进行了转换,导致url参数中只包含原来请求的地址信息,
而原来请求的参数信息则需要我们解析出来和地址信息一起重新组成本来的请求URL,因此方法开头的一段while就做了这个工作。
注意由于进入servlet之前参数信息已经被做过一次URLDecoder,因此这个时候参数信息中的中文信息传到真正要访问的servlet时解码会出现乱码,
因此我们再拼接参数信息之前又再一次通过URLEncoder.encode方法对所有参数信息进行了一次编码,这样就解决了中文的乱码问题。
在从远端服务器读取数据时,要显示的指定输入流的编码格式,这样才可以保证通过BufferReader读到的内容不会有乱码信息。
代理的这一部分测试我没有成功,不知道哪里错了!郁闷,主要代码如下:
修改后的新页面 proxy.html
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <script type="text/javascript"> var xml; var resultNode; function test(){ //第一步:创建XMLHttpRequest对象 resultNode = document.getElementById("result"); var name = document.getElementById("name").value; if (window.XMLHttpRequest) { xml = new XMLHttpRequest(); if (xml.overrideMimeType) { xml.overrideMimeType("text/xml"); } } else if (window.ActiveXObject) { var MSXML = ['MSXML2.XMLHTTP.6.0', 'MSXML2.XMLHTTP.5.0', 'MSXML2.XMLHTTP.4.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP']; for (var n = 0; n < MSXML.length; n++) { try { xml = new ActiveXObject(MSXML[n]); break; } catch (e) { } } } if (xml == null) { resultNode.innerHTML = "不能建立XMLHttpRequest对象"; return false; } //第二步:注册回调方法 xml.onreadystatechange = callback; //第三步:设置和服务器端交互的相应参数 name = encodeURI(encodeURI(name)); //第四步:设置向服务器发送的数据,启动和服务器端的交互 var url = "http://192.168.1.2:8080/ajaxTest/AJAXServlet"; url = convertURL(url);// //var url = "Proxy?http://192.168.1.2:8080/ajaxTest/AJAXServlet"; try { xml.open("POST", url, true);//post方式的第三部和第四步 xml.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");//这一步很重要 xml.send("name=" + name); } catch (e) { var msg = document.getElementById("msg"); msg.innerHTML = e; } } //第五步:判断和服务器端的交互是否完成,还要判断服务器端是否正确返回了数据 function callback(){ //alert(xml.readyState); if (xml.readyState == 4) { if (xml.status == 200) { resultNode.innerHTML = xml.responseText; } else { resultNode.innerHTML = "服务器故障"; } } } function convertURL(url){ if (url.substring(0, 7) == "http://") { url = url.replace("?", "&"); url = "Proxy?url=" + url; } return url; } </script> </head> <body> <input type="text" id="name" value=""> <input type="button" value="百度一下吧" onclick="test()"> <br/> <div id="result"></div><br/> <div id="msg"></div> </body> </html>
建一个Servlet,Proxy,映射为 /Proxy
import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.URL; import java.net.URLConnection; import java.net.URLEncoder; import java.util.Enumeration; import javax.servlet.http.HttpServlet; public class Proxy extends HttpServlet { private static final long serialVersionUID = 4185049099706154387L; protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, java.io.IOException { response.setContentType("text/html;charset=utf-8"); String url = request.getParameter("url"); StringBuffer param = new StringBuffer(); Enumeration enu = request.getParameterNames();//得到参数组成的枚举对象 int total = 0; while (enu.hasMoreElements()) { String name = (String) enu.nextElement(); if (!name.equals("url")) { if (total == 0) {//post方式,第一个参数前面没有字符,后面的参数前面有& param.append(name).append("=").append(URLEncoder.encode(request.getParameter(name), "UTF-8")); } else { param.append("&").append(name).append("=") .append(URLEncoder.encode(request.getParameter(name), "UTF-8")); } total++; } } PrintWriter out = response.getWriter(); if (url != null) { URL connect = new URL(url.toString()); URLConnection connection = connect.openConnection();//根据url建立连接并打开连接 connection.setDoOutput(true); OutputStreamWriter paramout = new OutputStreamWriter(connection.getOutputStream()); paramout.write(param.toString()); paramout.flush(); BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), "utf-8")); String line; while ((line = reader.readLine()) != null) { out.println(line); } paramout.close(); reader.close(); } } protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, java.io.IOException { response.setContentType("text/html;charset=utf-8"); StringBuffer url = new StringBuffer(); url.append(request.getParameter("url")); Enumeration enu = request.getParameterNames(); int total = 0; while (enu.hasMoreElements()) { String name = (String) enu.nextElement(); if (!name.equals("url")) { if (total == 0) {//get方式,第一个参数前面有?,后面的参数前面有& url.append("?").append(name).append("=") .append(URLEncoder.encode(request.getParameter(name), "UTF-8")); } else { url.append("&").append(name).append("=") .append(URLEncoder.encode(request.getParameter(name), "UTF-8")); } total++; } } PrintWriter out = response.getWriter(); if (url != null) { URL connect = new URL(url.toString()); BufferedReader reader = new BufferedReader(new InputStreamReader(connect.openStream(), "utf-8")); String line; while ((line = reader.readLine()) != null) { out.println(line); } reader.close(); } // http://www.sohu.com/index.html?name=123&id=000 // Proxy?url=http://www.sohu.com/index.html&name=123&id=000 // url=http://www.sohu.com/index.html&name=123&id=000 // http://www.sohu.com/index.html?id=000&name=123 } }
测试会报错: