用户在使用BS结构的程序时,经常会遇见类似如图 - 1所示的问题:
图- 1
用户填写信息后,向服务器发送请求,在点击发送后,客户端浏览器就会将当前页面销毁,以用于等待请求所对应的响应,然后将响应返回的页面进行加载。但往往信息发送到Web服务器后,有些数据是要进行有效性的验证的,如检查用户名是否重复,一旦这个信息经检查后不能使用,那么Web服务器返回的完整页面并不是用户期待看到的页面,也许只是一个简单的错误信息说明,于是,用户不得不再次填写合法信息及那些还没有验证的信息,等待服务器的再一次的验证。
在这个过程中,用户填写了大量信息,结果因为验证失败而不得不再次填写一遍表单数据,打断了用户的操作。
如果用户的信息反复验证后仍不能通过,那么这期间不仅仅耗费了网络资源,也占用了服务器的资源,降低了服务器的效率。
所以为了更好的提升用户的体验,以及提升服务器的效率,减少网络中不必要的数据的传送,AJAX技术从IE5开始逐渐被大家所接受,不仅仅提升了用户的操作体验,也提升了服务器端的效率。
Asynchronous JavaScript and Xml (异步的JavaScript和Xml)
AJAX是一种用来改善用户体验的技术,本质是使用XMLHttpRequest对象异步地向服务器发请求,并且发送请求的同时浏览器并不销毁页面,可以继续进行页面的操作,在服务器收到请求之后会返回部分数据,而不是一个完整的页面。浏览器接收到这些部分数据之后会以页面无刷新的效果更改页面中的局部内容。
整个过程有种多线程的感觉,页面当前可以继续操作,而背后已经在偷偷的向服务器发送了请求,但客户端不会有任何的觉察,不会打断客户端的操作。对于服务器来讲,接收的数据量是少的,返回的数据量也是少的,挺高了效率,减少了无谓的数据传送和判断。
AJAX的工作原理如 图 – 2 所示:
图- 2
在浏览器中添加了一个负责发送请求的AJAX对象,同时该对象事先会绑定一段事件处理函数。用户填写信息点击注册时,会调用AJAX对象的方法,让它来发送请求,AJAX对象发请求并不会影响页面的存在,所以在AJAX对象发送请求的同时,表单页面可以继续其他的工作。服务器接收到请求后获取数据,进行判断,响应时的数据不再是完整的页面,而是部分数据。当响应提供的部分数据到达客户端时,并不是直接由浏览器展示,而是由事先准备好的事件处理函数接收、解析。将部分数据取出来后由JavaScript代码控制这些数据及样式,然后更新至页面的某一个位置。
整个过程中,Ajax负责发送请求,也负责接受返回的响应,并将响应中的数据更新至页面中。
使用如下代码可以获得到AJAX对象:
function getXhr(){ var xhr = null; if(window . XMLHttpRequest){ xhr = new XMLHttpRequest(); }else{ xhr = new ActiveXObject('Microsoft . XMLHTTP'); } return xhr; }
AJAX对象本身是浏览器中的一个对象,但在IE中表现为一个ActiveX。所以在使用JavaScript语言进行创建该对象时,需要区分不同的浏览器。
经过判断 window.XMLHttpRequest存在时,直接new这个核心的XMLHttpRequest对象即可。如果这个对象不存在,就有可能是IE浏览器,所以创建ActiveXObject对象即可。
创建这个XMLHttpRequest对象是进行后续操作的基础,正是这个对象的存在才使得异步请求得以实现。
有时候我们会把XMLHttpRequest对象称为AJAX对象,或异步对象。它的属性和方法如 表-1 所示
表-1 AJAX对象的属性和方法
onreadystatechange需要绑定一个事件处理函数,该函数用来处理readystatechange事件。当AJAX对象的readyState的值发生了改变时,该事件由系统触发。比如,从0变成了1,就会产生readystatechange事件。在当前不同的处理状态下,可以进行状态的捕获,做些相应处理。每一次的状态变化都会触发onreadystatechange绑定的处理函数,只是大部分时候我们只需要知道从3变为4,即当前readyState为4时这个状态,此时数据响应已返回,并接收成功,等待下一步的处理。大部分的格式如下:
xhr.onreadystatechange=function(){ //… … //处理返回的数据 }
readyState代表请求的状态,使用不同的数字代表一个状态。
状态的变化时数字从0到1到2依次的一系列的变化,判断不同的数字状态可以提供更精确的处理。最主要的情况是判断4这个状态,也就是最终状态,可以进行数据解析及展示在页面中了。
对于状态的判断书写代码的格式如下:
xhr.onreadystatechange=function(){ if(xhr.readyState == 4){ var txt = xhr.responseText; //展示数据到页面 } }
但是状态并不能保证回来的数据就是我们想要的数据,也可能是发回的错误提示。所以要想保证接受到的数据就是成功响应的数据,还需添加对另一个属性的判断——status。这个属性代码响应的状态码,200代表成功,404代表没有找到资源,500代表服务器发生了运行异常。
以下代码为一般使用AJAX的逻辑判断结构:
function check_username(){ //获得ajax对象 var xhr = getXhr(); //发送请求 var uri = 'check_username.do?username=' + $F('username'); xhr.open('get',encodeURI(uri),true); xhr.onreadystatechange=function(){ /*只有xhr的readyState等于4时,xhr才获得了服务器返回的所有数据。*/ if(xhr.readyState == 4 && xhr.status == 200){ //正确数据 var txt = xhr.responseText; $('username_msg').innerHTML = txt; }else{ //发生了错误 $('username_msg').innerHTML ='验证用户名出错'; } }; $('username_msg').innerHTML = '正在检查...'; xhr.send(null); }
使用代码获取AJAX对象时,由于需要进行浏览器的判断,所以代码较多,封装到一个函数中进行。同时函数外要能够获取到AJAX对象,所以需要在函数外声明一个单独的变量存储函数创建的异步对象,基本的代码结构如下:
var xhr = getXhr(); function getXhr(){ var xhr = null; if(window . XMLHttpRequest){ xhr = new XMLHttpRequest(); }else{ xhr = new ActiveXObject('Microsoft.XMLHTTP'); } return xhr; }
发送请求的主要代码如下:
xhr.open('get',URI,true);
open()方法可以理解为准备工作,填写发送请求前的信息的准备。
注意:该方法还没有真正的发送请求。
做好准备工作后,需要指定事件处理函数,最后通过send()方法完成实际的发送请求。代码结构如下:
xhr.open('get‘,‘xx.do?uname=Bear',true); xhr.onreadystatechange = fn; xhr.send(null);
发送POST请求的主要代码如下:
xhr.open('post',URI,true);
open()方法中填写“post”,说明请求方式。URI说明请求的目的地,但由于POST请求提交的数据一般是放在请求体中,所以URI里面并不像GET方式那样需要尾随一些参数。第三个参数用来标识同步还是异步提交。
但POST提交方式与GET方式有一个非常显著的不同,就是缺少一个消息头信息,“content-type”。所以在POST请求时,要使用编码的形式来为消息头添加这个说明。代码如下:
xhr.setRequestHeader('content-type',‘application/x-www-form-urlencoded');
这些准备工作做好之后,就是指定事件响应代码,最后就是通过 send()方法实际的发送请求。GET方式时,send方法不需要参数,传入null。但POST提交方式时,就需要在send方法中传入表单要提交的数据,以“name=value”对的字符串形式传入即可。
发送POST请求的基本代码结构如下:
xhr.open('post',‘ xx.do‘ , true); xhr.setRequestHeader('content-type',‘application/x-www-form-urlencoded'); xhr.onreadystatechange = fn; xhr.send('uname = Bear');
综合上面的分解讲解,总结客户端AJAX发请求的基本步骤如下:
客户端通过JavaScript代码实现发送请求以后,服务器端需要提供响应的代码来进行处理,并有能力返回部分数据。对于服务器端来讲,不管请求是浏览器发出的,还是AJAX对象发出的,并没有太大区别,只要使用getParameter方法获取到数据,调用业务逻辑进行处理,返回数据即可。这样的逻辑中,只有第三步会与以前稍有不同。要么返回完整页面,要么返回一小段文本进行说明。
服务器端代码结构如下:(判断用户名是否合法)
import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.*; public class ActionServlet extends HttpServlet { public void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("utf-8"); response.setContentType( "text/html;charset=utf-8"); PrintWriter out = response.getWriter(); String uri = request.getRequestURI(); String action = uri.substring(uri.lastIndexOf("/"), uri.lastIndexOf(".")); if(action.equals("/check_username")){ String username = request.getParameter("username"); if("王小熊".equals(username)){ out.println("用户名已经存在"); }else{ out.println("可以使用"); } } out.close(); } }
客户端发送请求,服务器端返回响应,客户端接收响应后就需要处理服务器返回的信息。对于HTML页面来讲,返回数据的处理就是使用JavaScript代码将数据安插到已有页面中的某个位置。而这个响应的返回时机是由系统捕获的,所以事件处理函数在发送请求前即以指定,调用和执行是由系统来完成的。要想获取返回的文本,通过Ajax对象的responseText属性就可以读取到。
事件处理函数的代码结构如下:
xhr.onreadystatechange=function(){ if(xhr.readyState == 4 && xhr.status==200){ var txt = xhr.responseText; //定位DOM节点,添加文本,实现刷新 $('s2').innerHTML = txt; } };
乱码的根本原因在于编码和解码的方式不同而产生的。
IE浏览器提供的AJAX对象会使用GBK字符集对请求参数进行编码,而其它浏览器会使用UTF-8来编码。也就是不同的浏览器在发送请求时数据编码就已经不一致了。
对于服务器来讲,默认情况下会使用ISO-8859-1进行解码,这种解码格式是Tomcat的配置文件中声明的。
于是,内容经不同的浏览器的编码已经生成了不同的结果,而这个结果到达服务器之后,又采用了一种与哪个浏览器都不一样的解码方式来解析,于是在服务器端数据从取出来的一刻开始就是错误的了,所以后续的所有处理也都是基于错误的数据,肯定得不到正确的结果了。
要想解决乱码问题,就需要在客户端和服务器端统一编码和解码的方式即可。
解决乱码问题需要在服务器端和客户端保持编码和解码的一致。GET请求时,在服务器端通过修改配置文件实现服务器端固定的解码格式UTF-8。在客户端提交请求时,由于数据尾随在URI中,所以使用JavaScript语言的encodeURI方法可以实现UTF-8编码格式的编码。步骤如下:
step1,指定字符集进行解码
Tomcat可以修改conf/server.xml文件中<Connector URIEncoding="UTF-8"> ,使得tomcat按UTF-8方式解码。
step2,使用encodeURI对请求地址进行编码。encodeURI会使用utf-8对请求地址中的中文参数进行编码。
其实问题的根本原因就是IE的与众不同。修改完成后,重启tomcat,不用IE来运行就会发现可以正常读取表单的get方式提交的中文。
针对 IE :
var uri = ‘xxx.do?uname='+$F('username'); xhr.open('get‘ , encodeURI(uri) , true);
POST请求时乱码问题产生的原因依然是编码与解码的不同造成的。因为所有浏览器在进行POST方式提交时都是使用UTF-8方式进行编码的,到了服务器端则默认使用ISO-8859-1方式来解码。所以只需要修改服务器端的解码格式,保证是UTF-8的方式来解码就可以避免乱码。
request.setCharacterEncoding(“UTF-8”);
注 : 火狐就不用这句代码,是因为这个浏览器会在发送的请求数据包中告诉服务器,它是以哪种方式进行的数据编码。
通用的避免出现乱码的代码格式如下:
服务器端:
request.setCharacterEncoding("utf-8"); response.setContentType("text/html;charset=utf-8");
客户端:
function check_username(){ //获得ajax对象 var xhr = getXhr(); //发送请求 var uri = 'check_username.do?username=' + $F('username'); xhr.open('get',encodeURI(uri) ,true); xhr.onreadystatechange=function(){ /* 只有xhr的readyState等于4时, xhr才获得了服务器返回的所有数据。 */ if(xhr.readyState == 4){ if(xhr.status == 200){ //正确数据 var txt = xhr.responseText; $('username_msg').innerHTML = txt; }else{ //发生了错误 $('username_msg').innerHTML = '验证用户名出错'; } } }; $('username_msg').innerHTML = '正在检查...'; xhr.send(null); }