(如何根治HTTP請求響應中的亂碼)tomcat中的字符集問題,測試以及總結


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();
        }

測試結果

 

也就是這樣子啦。。。

 

總結:

1、若返回的contentType為text/html,而charset缺省,會去尋找meta標簽

2、meta標簽如果也缺少,則火狐和chrome都會使用GBK編碼

2、若contentType整個都缺省,則會使用iso-8859-1編碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM