应答消息的第一行为状态行,其中包括了HTTP版本号,状态码和对状态码进行简短解释的消息;状态行的最后是回车换行.状态码由3位数字组成,有5类:
- 1XX 保留
- 2XX 表示成功
- 3XX 表示URL已经被移走
- 4XX 表示客户错误
- 5XX 表示服务器错误
例如:415,表示不支持改媒体类型;503,表示服务器不能访问.最常见的是200,表示成功.常见的报头有:Last_Modified(最后修改时间),Content_Type(消息内容的MIME类型),Content_Length(内容长度)等. 在报头行之后也是一个回车换行,用以表示应答消息的报头部分的结束,以及应答消息实体的开始. 下面是一个应答消息的例子:
HTTP/1.0 200 OK Date: Moday,07-Apr-97 21:13:02 GMT Server:NCSA/1.1 MIME_Version:1.0 Content_Type:text/html Last_Modified:Thu Dec 5 09:28:01 1996 Coentent_Length:3107
<HTML><HEAD><TITLE>...</HTML> | 那么 GET 和 POST 有什么区别? 区别就是一个在 URL 请求里面附带了表单参数和值, 一个是在 HTTP 请求的消息实体中. 用下面的例子可以很容易的看到同样的数据通过GET和POST来发送的区别, 发送的数据是 username=张三 : GET 方式, 浏览器键入 http://localhost?username=张三
GET /?username=%E5%BC%A0%E4%B8%89 HTTP/1.1 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, */* Accept-Language: zh-cn Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.1.4322) Host: localhost Connection: Keep-Alive | POST 方式:
POST / HTTP/1.1 Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, */* Accept-Language: zh-cn Content-Type: application/x-www-form-urlencoded Accept-Encoding: gzip, deflate User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.1.4322) Host: localhost Content-Length: 28 Connection: Keep-Alive
username=%E5%BC%A0%E4%B8%89 | 比较一下上面的两段文字, 您会发现 GET 方式把表单内容放在前面的请求头中, 而 POST 则把这些内容放在请求的主体中了, 同时 POST 中把请求的 Content-Type 头设置为 application/x-www-form-urlencoded. 而发送的正文都是一样的, 可以这样来构造一个表单提交正文: encodeURIComponent(arg1)=encodeURIComponent(value1)&encodeURIComponent(arg2)=encodeURIComponent(value2)&..... 注: encodeURIComponent 返回一个包含了 charstring 内容的新的 String 对象(Unicode 格式), 所有空格、标点、重音符号以及其他非 ASCII 字符都用 %xx 编码代替,其中 xx 等于表示该字符的十六进制数。 例如,空格返回的是 "%20" 。 字符的值大于 255 的用 %uxxxx 格式存储。参见 JavaScript 的 encodeURIComponent() 方法. 下面就讨论一下如何在 JavaScript 中执行一个 GET 或者 POST 请求. 如果您用过 Java, 那么您可能熟悉下列的用 java.net.URLConnection 类进行 POST 操作的代码(参考 Java Tip 34: POSTing via Java ):
URL url; URLConnection urlConn; DataOutputStream printout; // URL of CGI-Bin or jsp, asp script. url = new URL ("somepage"); // URL connection channel. urlConn = url.openConnection(); // ...... // No caching, we want the real thing. urlConn.setUseCaches (false); // Specify the content type. urlConn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); // Send POST output. printout = new DataOutputStream (urlConn.getOutputStream ()); String content = "name=" + URLEncoder.encode ("Buford Early") + "&email=" + URLEncoder.encode ("buford@known-space.com"); printout.writeBytes (content); printout.flush (); printout.close (); | 以上的代码向 somepage 发送了一次 POST 请求, 数据为 name = Buford Early, email = buford@known-space.com. 用 JavaScript 来执行 POST/GET 请求是同样的原理, 下面的代码展示了分别用 XMLHttpRequest 对象向 somepage 用 GET 和 POST 两种方式发送和上例相同的数据的具体过程: GET 方式
var postContent = "name=" + encodeURIComponent("Buford Early") + "&email=" + encodeURIComponent("buford@known-space.com"); xmlhttp.open("GET", "somepage" + "?" + postContent, true); xmlhttp.send(null); | POST 方式
var postContent = "name=" + encodeURIComponent("Buford Early") + "&email=" + encodeURIComponent("buford@known-space.com"); xmlhttp.open("POST", "somepage", true); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send(postContent); | 至此希望你已经能够理解如何用 JavaScript 中的 XMLHttpRequest 对象来执行 GET/POST 操作, 剩下的工作就是您如何来构造这些提交的参数了, 最后我给出一个将现有的 form 提交代码修改为异步的 AJAX 提交的代码(注意目前作者还不知道如何让 file 上传表单域也能异步上传文件). 首先请看两个 JavaScript 函数:
// form - the form to submit // resultDivId - the division of which to display result text in, in null, then // create an element and add it to the end of the body function ajaxSubmitForm(form, resultDivId) { var elements = form.elements;// Enumeration the form elements var element; var i;
var postContent = "";// Form contents need to submit
for(i=0;i<elements.length;++i) { var element=elements[i];
if(element.type=="text" || element.type=="textarea" || element.type=="hidden") { postContent += encodeURIComponent(element.name) + "=" + encodeURIComponent(element.value) + "&"; } else if(element.type=="select-one"||element.type=="select-multiple") { var options=element.options,j,item; for(j=0;j<options.length;++j){ item=options[j]; if(item.selected) { postContent += encodeURIComponent(element.name) + "=" + encodeURIComponent(item.value) + "&"; } } } else if(element.type=="checkbox"||element.type=="radio") { if(element.checked) { postContent += encodeURIComponent(element.name) + "=" + encodeURIComponent(element.value) + "&"; } } else if(element.type=="file") { if(element.value != "") { postContent += encodeURIComponent(element.name) + "=" + encodeURIComponent(element.value) + "&"; } } else { postContent += encodeURIComponent(element.name) + "=" + encodeURIComponent(element.value) + "&"; } }
alert(postContent);
ajaxSubmit(form.action, form.method, postContent); }
// url - the url to do submit // method - "get" or "post" // postContent - the string with values to be submited // resultDivId - the division of which to display result text in, in null, then // create an element and add it to the end of the body function ajaxSubmit(url, method, postContent, resultDivId) { var loadingDiv = document.getElementById('loading'); // call in new thread to allow ui to update window.setTimeout(function () { loadingDiv.innerText = "Loading...."; loadingDiv.style.display = ""; }, 1);
// code for Mozilla, etc. if (window.XMLHttpRequest) { xmlhttp=new XMLHttpRequest(); } // code for IE else if (window.ActiveXObject) { xmlhttp=new ActiveXObject("Microsoft.XMLHTTP"); }
if(xmlhttp) { xmlhttp.onreadystatechange = function() { // if xmlhttp shows "loaded" if (xmlhttp.readyState==4) { if(resultDivId) { document.getElementByID(resultDivId).innerHTML = xmlhttp.responseText; } else { var result = document.createElement("DIV"); result.style.border="1px solid #363636"; result.innerHTML = xmlhttp.responseText; document.body.appendChild(result); }
loadingDiv.innerHTML = "Submit finnished!"; }
};
if(method.toLowerCase() == "get") { xmlhttp.open("GET", url + "?" + postContent, true); xmlhttp.send(null); } else if(method.toLowerCase() == "post") { xmlhttp.open("POST", url, true); xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xmlhttp.send(postContent); } } else { loadingDiv.innerHTML = "Can't create XMLHttpRequest object, please check your web browser."; }
}
| 函数 ajaxSubmitForm 将表单要提交的内容进行封装, 然后调用 ajaxSubmit 函数来执行真正的异步提交, 表单提交后所返回的结果则显示在给定的 DIV 容器中或者没有指定参数时用 DOM 对象动态生成一个 DIV 容器来显示结果并添加到页面末尾. 这样, 对原来的表单只需要改动一个地方就可以将原来的表单提交改为异步模式, 即在 form 标签里加入: onSubmit="ajaxSubmitForm(this);return false;" 即可, return false 确保表单不会被浏览器同步提交. 完整的例子请看这里. ----------------- 引用结束 ----------------- OK, 希望至此为止您已经理解了如何用 AJAX 来正确的执行 GET/POST. 如果这个问题您解决了, 可是说后台的乱码问题就和你直接通过表单提交几乎没有区别了. 这个方法的具体封装已经在附件的 ajax_common.js 中了. 至此也该贴出来我们的 GBK 编码的客户端页面的内容了:
<html>
<head> <meta http-equiv="Content-Type" content="text/html; charset=gbk"> <title>AJAX Form Submit Test</title> <script src='ajax_common.js'></script>
</head>
<body> 本页面的编码是中文.<br/> <meta http-equiv="Content-Type" content="text/html; charset=gbk"><br/> <b>测试过的服务器:</b><br/> Resin 3.0.18<br/> Tomcat 5.5.20<br/> Tomcat 5.0.30<br/> <h3>AJAX Form Submit Test</h3> Fill the form and then click submit<br> 提交方式: POST<br> <form method="POST" id="form1" name="form1" action="form_action.jsp" onSubmit="former.ajaxSubmitForm();return false;"> <p><input type="hidden" name="hidden1" value="hiddenValue"> text:<input type="text" name="textf&1" size="20" value="text文本&1"> checkbox:<input type="checkbox" name="checkbox1" value="ON" checked> radio:<input type="radio" value="V1" checked name="radio1"> select:<select size="1" name="select1"> <option selected value="option1">D1</option> </select> <br> <br> <input type="submit" name="B1" value="submit"> <input type="reset" name="B2" value="reset"> </p> </form>
提交方式: GET<br> <form method="GET" id="form2" name="form2" action="form_action.jsp" onSubmit="former2.ajaxSubmitForm();return false;"> <p><input type="hidden" name="hidden1" value="hiddenValue"> text:<input type="text" name="text文本&2" size="20" value="text文本&2"> checkbox:<input type="checkbox" name="checkbox1" value="ON" checked> radio:<input type="radio" value="V1" checked name="radio1"> select:<select size="1" name="select1"> <option selected value="option1">D1</option> </select> <br> <br> <input type="submit" name="B1" value="submit"> <input type="reset" name="B2" value="reset"> </p> </form>
<div id="loading" style="display:none; position:absolute; border:1px solid orange; height:20px; width:600; left: 93px; top: 112px; background-color: #FFFFCC; cursor:pointer;" title="Click to hide" onClick="this.style.display='none';"></div>
<div id="resultDiv" style="border:1px solid orange; background-color: #FFFFCC; cursor:pointer;" title="Click to hide" onClick="this.style.display='none';"> Form 1 的提交结果将会显示在这里. </div>
<script type="text/javascript"> var former = new AjaxFormer($('form1'), 'resultDiv'); var former2 = new AjaxFormer($('form2')); </script> </body>
</html>
可以看到我们的确使用的是 GBK 编码, 浏览器打开的时候自动选择的编码也是简体中文. 那么第二个关键点就是服务器端的表单数据读取了.这个问题跟具体的服务器有很大关系. 对于 Resin 服务器来说, 问题很少, 基本上不论是 POST 和 GET, 出乱码的概率都比较小. 但是 Tomcat 就不敢恭维了, 这大概也是开源产品和商业产品的区别, 缺乏前后一致性和兼容性, 因为开源的不需要提供技术支持. Tomcat 的 GET/POST 的编码处理方式不同的版本都不一样, 就像 Eclipse/Netbeans 新版本从来不需要兼容老版本的插件 API 一样, Hibernate/Struts/Spring 也是一样, 所以学 Java 的很累. 当然, 这就是免费/开源的代价. 跑题了. 因此我们的服务器端代码大部分都是对 Tomcat 的乱码问题的解决(POST的没有问题, 主要是 GET 方法的).
<%@ page contentType="text/html; charset=gbk" pageEncoding="gbk"%> <html> <% //Send some headers to keep the user's browser from caching the response. response.addHeader("Expires", "Mon, 26 Jul 1997 05:00:00 GMT" ); response.addHeader("Last-Modified", new java.util.Date().toGMTString()); response.addHeader("Cache-Control", "no-cache, must-revalidate" ); response.addHeader("Pragma", "no-cache" ); // This will emulate a network delay, for 2 sec. //Thread.currentThread().sleep(2000);
request.setCharacterEncoding("utf-8"); %>
<%!
/** * 转换字符串的内码. * * @param input * 输入的字符串 * @param sourceEncoding * 源字符集名称 * @param targetEncoding * 目标字符集名称 * @return 转换结果, 如果有错误发生, 则返回原来的值 */ public static String changeEncoding(String input, String sourceEncoding, String targetEncoding) { if (input == null || input.equals("")) { return input; }
try { byte[] bytes = input.getBytes(sourceEncoding); return new String(bytes, targetEncoding); } catch (Exception ex) { } return input; }
/** * 一个类似于 JavaScript 的 escape 函数的功能, 确保乱码可以正确传输. */ public static String escape(String src) { int i; char j; StringBuffer tmp = new StringBuffer(); tmp.ensureCapacity(src.length() * 6); for (i = 0; i < src.length(); i++) { j = src.charAt(i); if (Character.isDigit(j) || Character.isLowerCase(j) || Character.isUpperCase(j)) tmp.append(j); else if (j < 256) { tmp.append("%"); if (j < 16) tmp.append("0"); tmp.append(Integer.toString(j, 16)); } else { tmp.append("%u"); tmp.append(Integer.toString(j, 16)); } } return tmp.toString(); } %>
<head> <title>Test form action page</title> </head> <body> 这是 GBK 编码版本的后台表单提交页面.<br/>
<% boolean isTomcat = application.getServerInfo().toLowerCase().indexOf("tomcat") != -1; %>
Form submit method:<%=request.getMethod()%><br/> The form content u send is:<br/> <% java.util.Enumeration e = request.getParameterNames();
while (e.hasMoreElements()) { String name = (String)e.nextElement(); String value = request.getParameter(name);
if(isTomcat && request.getMethod().equalsIgnoreCase("GET")) { name = changeEncoding(name, "ISO8859-1", "UTF-8"); value = changeEncoding(value, "ISO8859-1", "UTF-8"); } out.println("<b>" + name + "</b> = " + value + "<br/>"); }
// 给前台返回一个可以执行的脚本 //response.addHeader("response_script", changeEncoding("alert('提交完成');", "ISO8859-1", "UTF-8")); response.addHeader("response_script", escape("alert('提交完成');")); %>
</body> </html>
booleanisTomcat=application.getServerInfo().toLowerCase().indexOf("tomcat") !=-1; 这一句主要针对 Tomcat 进行处理, 如果是 GET 方法的话, 就需要将表单参数从 ISO8859-1 转换到 UTF-8 (注意不是 GBK, 貌似 Tomcat 很喜欢 UTF-8?). 其它的地方和原来的 UTF-8 版本的没有区别. 当然如果您的站点应该用过滤器来更方便的解决这个问题.
小结:
1. 使用一致的字符集很重要, 要么全是 GBK, 要么全是 UTF-8, 如果有条件, 就全部用 UTF-8, 那样工作量是最小的;
2. 用 AJAX 提交的时候一定要按照 HTTP 的规范来, 做到和浏览器尽量兼容, 尤其是 POST 的时候不要再往 URL 地址里加参数了, 你那样是违规! 后果就是有的服务器会不搭理你传递的这些参数! 还是如我所讲, 参数提交之前要用 encodeURIComponent() 来转化, 这也是为了符合浏览器的习惯做法.
3. 后台如果读取参数有乱码, 就尽量多在 ISO8859-1, GBK, UTF-8 中间多转换几次试试, 可以试试偶写的那个 changeEncoding() 方法, 把几个转换后的表单值都列出来, 一定有一个是正确的, 总是可以解决问题的. 这个本来不应该是偶们的任务, 但是写服务器的人是老美, 尤其是 Tomcat 作者, 只熟悉 ISO8859-1.
4. 鉴于 TOMCAT 读取 POST 参数的时候很少出问题, 因此建议AJAX提交表单的时候多用 POST 方法, 尽量不用 GET. |