HTTP服務器的通常作用可以理解成,接收來自瀏覽器的請求,讀取其中的信息,並返回http格式的數據
其中,瀏覽器發送的數據主要以三種形式傳遞,get方式提交的參數,post方式的參數,以及cookie中攜帶的參數三類
而服務器端,生成http返回數據的形式主要有3種
其中,JSP在本質上與Servlet無異,一般可認為JSP在Servlet的基礎上,加上了Html的框架,不過如果把JSP代碼中的HTML代碼去掉,兩者基本就是一個東西了。
整個流程中,需要注意到字符集問題的地方,從動作而言,分為四步:
字符集問題出現在信息從一級流通到另一級的過程中,也就是這4個動作當中。要規避字符集問題,需要了解四個動作中,分別的,其使用的字符集的確認方式。
也就是說,
瀏覽器發送數據時,采用哪種字符集?
服務器解析瀏覽器發送的數據時,采用哪種字符集?
服務器在返回數據時,使用哪種字符集?
瀏覽器解析服務器返回的數據時,使用哪種字符集?
這么四個問題。
以下就這四個問題展開討論
1、瀏覽器發送數據時,采用哪種字符集?
瀏覽器發起一個請求,需要通過URL訪問
根據形式分為,直接在瀏覽器中輸入URL訪問,以及通過鏈接跳轉訪問,通過鏈接跳轉訪問則包括form表單以及超鏈接兩種形式
而無論哪種形式,最終瀏覽器都會將其包裝成HTTP格式進行訪問,也即如下格式:
POST /MyTestProject6/Test_Servlet_002 HTTP/1.1
Host: localhost
Connection: keep-alive
Content-Length: 39
Cache-Control: max-age=0
Origin: http://localhost
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://localhost/MyTestProject6/Test_003.html
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: JSESSIONID=1420F43C4B8259D6FCCD46E87B095DB3; _ga=GA1.1.1822121407.1535547634
request信息中,並不會指定編碼集。
那參數在傳遞過程中,對中文進行編碼的格式如何確定呢?
首先將瀏覽器訪問服務器的動作分為兩種
1、在URL欄中直接輸入URL訪問
2、在已經呈現的HTML頁面中跳轉
直接在URL欄中輸入的中文,會以瀏覽器默認的編碼集進行編碼。
如此去訪問,火狐瀏覽器的結果是:
chrome瀏覽器的結果也是:
而其他的的訪問,也即是由頁面生成的URL,其內容的編碼格式取決於該頁面在編碼時使用的字符集
一定程度上,也就是頁面中的<meta charset="XXX">標簽,該標簽標注了該頁面的編碼格式
(不過當然沒這么簡單,之后會在第四部分詳細說明)
當頁面中<meta charset="XXX">標簽缺省時。瀏覽器會采用默認字符集對頁面進行編譯,這也是由這個頁面所產生的URL在生成request時所采用的字符集。
總結:瀏覽器發送數據時,采用哪種字符集?
1、直接輸入URL的,依瀏覽器(chrome:utf-8,火狐:gbk)
2、頁面采用何種字符集編碼,則發送數據時采用何種字符集
3、頁面未指定字符集時,chrome和火狐都默認使用GKB進行編碼
2、服務器解析瀏覽器發送的數據時,使用何種字符集?
上文提到,request中並不會攜帶關於request的內容由哪種字符集信息。
因此,服務器在解析數據時,隨緣。。。
比如某個用戶在URL中自己手動輸入中文參數,可能會影響。
但大多數URL都由服務器自身的頁面發出,統一字符集的話,一般不會受到字符集問題的影響。
以下,以TOMCAT9為例,闡述如何應對GET請求,POST請求的處理方式。
總所周知,一條URL請求,會由瀏覽器包裝成HTTP格式,再發送到服務器,因此服務器接受到的數據是一個數據包,而非單單一個URL字符串。
其中訪問路徑信息path在第一行的中間,並且以get形式提交的數據顯示在了path當中。
tomcat7以上的版本,對於path的內容,會自動用utf-8去解碼。其也可以在server.xml中去配置。
而tomcat7以及以下的版本,默認的字符集依然是iso-8859-1。因此需要通過對應的格式去decode。
不過TOMCAT9自動默認UTF-8也有不好的地方,get請求的參數默認就直接UTF-8編碼了,也就是無論GBK還是UTF-8發送過來都成了UTF-8。也就無法讀取GET里面的內容了
而POST請求,TOMCAT並不會自動解碼,而默認的字符集是iso-8859-1,只需要設置其為對應的字符集即可,也就是
request.setCharacterEncoding("XXX");
此外,COOKIE數據也是瀏覽器在訪問服務器時會攜帶的,但COOKIE數據不能包含中文(使用iso-8859-1編碼)
而COOKIE數據本身就是服務器原先自己添加進去的,因此需要按照添加時轉換所使用的字符集去解析。
總結:服務器解析瀏覽器發送的數據時,使用何種字符集?
1、首先參照上一節中描述的get數據和post數據在瀏覽器端編碼的規則
2、get數據一般默認iso-8859-1,tomcat7以上get請求默認utf-8,server.xml可以設置默認讀取方式,默認GBK但傳入UTF-8會很尷尬
3、post數據默認iso-8859-1,通過設置request.setCharacterEncoding("XXX")可以修改(在每次請求傳入時),也可以將這一步寫入filter中。
4、COOKIE數據默認iso-8859-1,按照服務器之前存入COOKIE時的字符集去解碼
3、服務器返回數據時,使用何種字符集?
服務器返回http數據時,一般有三種方式。
不過如果最終返回的是頁面,三種方式最終返回的數據,在瀏覽器看來是沒啥區別的。
(1)servlet
首先測試servlet,僅寫入這么一條service的內容,不做其他設置
response.getWriter().append("Served at: ").append(request.getContextPath());
查看瀏覽器端接收的數據中response的head
結合頁面顯示效果,不難發現
如果未設置contentType,瀏覽器會使用默認的iso-8859-1進行解析
如果設置了contentType,比如:
response.setContentType("text/html;charset=utf-8");
response.getWriter().append("測試直接傳輸一個中文");
則瀏覽器會使用對應的字符集進行解析,也就是參照contentType中設置的charset
HTTP/1.1 200 Content-Type: text/html;charset=utf-8 Content-Length: 104 Date: Fri, 14 Sep 2018 03:14:30 GMT
BUT,需要注意的是,最終傳輸的數據的字符集需要與此時設置的字符集保持一致。
不一致的情況包括而不限於:
比如上述代碼的.java文件字符集是其他的,比如
而代碼中設置的字符集為UTF-8,因此最終頁面會亂碼
這也是JSP跟HTTP頁面中需要注意的
(2)html頁面
在HTML頁面中,主要通過<meta charset="xxx"> 來控制頁面使用的字符集。
<!DOCTYPE html> <html> <head> <meta charset="gbk"> <title>Insert title here</title> </head> <body> 中文 </body> </html>
HTTP/1.1 200
Accept-Ranges: bytes
ETag: W/"300-1536840847318"
Last-Modified: Thu, 13 Sep 2018 12:14:07 GMT
Content-Type: text/html
Content-Length: 300
Date: Thu, 13 Sep 2018 12:14:10 GMT
不難看出,在訪問HTTP頁面時,其response的頭中,根本沒有標注其所使用的字符集
可以理解成,服務器在響應返回html頁面時,並不會對其內容進行解析,而是直接發送過去,因此服務器也不知道該html的字符集
而瀏覽器在接收到response相應之后,也是在讀取response的content部分中的meta標簽才確定字符集。
HTML文件同樣需要注意,最終傳輸的數據的字符集需要與此時設置的字符集保持一致。
缺省時,chrome和火狐都默認使用gkb編碼(應對contentType為text/html)
(3)JSP頁面
在JSP文件中,可以設置的字符集格式文件包括pageEncoding,contentType,以及其html代碼中的meta標簽。
不過contentType可以缺省,contentType缺省時服務器會根據pageEncoding設置contentType中的charset。
比如:
<%@page pageEncoding="utf-8"%> 這是一個中文
HTTP/1.1 200
Content-Type: text/html;charset=utf-8
Content-Length: 22
Date: Thu, 13 Sep 2018 12:22:49 GMT
而如果在已經有pageEncoding的情況下再去設置contentType的charset,后者會覆蓋前者,比如:
<%@page contentType="text/html;charset=gbk" pageEncoding="utf-8"%> 這是一個中文
HTTP/1.1 200
Content-Type: text/html;charset=gbk
Content-Length: 16
Date: Thu, 13 Sep 2018 12:24:04 GMT
當contentType設置了時,pageEncoding可以缺省,此時會默認使用contentType中設置的字符集作為pageEncoding的字符集。
此時依然需要注意,最終傳輸的數據的字符集需要與此時設置的字符集保持一致。
但此時,如果字符集沖突,其沖突是在JSP的編譯階段,而不是在瀏覽器上的顯示階段。
比如,當我設置了pageEncoding,但是跟.jsp文件采用的字符集不同時,其結果是在編譯而成的.java文件中就出現了亂碼
比如
<%@page pageEncoding="utf-8"%> 這是個中文
則訪問亂碼
其.java文件中就已經出現了亂碼
response.setContentType("text/html;charset=utf-8");
pageContext = _jspxFactory.getPageContext(this, request, response,
null, true, 8192, true);
_jspx_page_context = pageContext;
application = pageContext.getServletContext();
config = pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
_jspx_out = out;
out.write("\r\n");
out.write("���Ǹ�����\r\n");
當pageEncoding和contentType都缺省時,JSP編譯器會很暈。。然后用iso-8859-1編碼
總之!!JSP頁面就是不用meta里面設置的字符集。。。
<%@page%> <!DOCTYPE html> <html> <head> <meta charset="gbk" /> <title>Insert title here</title> </head> <body>我是個中文 </body> </html>
因為JSP在編譯過程中就在response的head中指定了字符集吖o(* ̄▽ ̄*)ブ
HTTP/1.1 200
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 136
Date: Thu, 13 Sep 2018 12:34:10 GMT
總結:服務器返回數據時,使用何種字符集?
1、servlet端需要設置contentType,注意與代碼的編碼文件格式保持一致,缺省時瀏覽器使用iso-8859-1。
2、html文件需要設置<meta charset>,注意與html文件的編碼格式保持一致,缺省時瀏覽器使用gbk。
3、jsp文件需要設置contentType或pageEncoding,注意與jsp文件的編碼格式保持一致,缺省時瀏覽器使用gbk,但JSP編譯器會通過iso-8859-1去編譯。
4、瀏覽器解析數據時,采用何種字符集
依照上文,大致能概括出瀏覽器在解析數據時使用的邏輯,以及不同charset設置的優先級和覆蓋關系:
瀏覽器在解析時使用的字符集,依照response的head中的contentType中的charset參數。
當該參數缺省時,會去尋找文件中的<meta charset>標簽。
都缺省時,使用瀏覽器默認字符集。
如果head中沒有contentType,則使用iso-8859-1。
可以在servlet中測試如下:
ServerSocket ss = new ServerSocket(8081); while (true) { Socket s = ss.accept(); PrintWriter pw = new PrintWriter(s.getOutputStream()); pw.println("HTTP/1.1 200"); pw.println("Content-Type: text/html"); pw.println(); pw.println("<meta charset='utf-8'>"); pw.println("中文"); pw.flush(); s.close(); }
測試結果
也就是這樣子啦。。。