Web應用中瀏覽器與服務端的編碼和解碼
【1】基本概念
有資訊交換就會產生編碼、傳輸、解碼三個過程。編碼是資訊從一種形式轉變成另一種形式的過程,正如人類的語言通過聲帶編碼,轉換成聲波。解碼是編碼的逆函式,耳膜接收聲波,通過腦神經解碼成人類文化所能理解的資訊。
字符集是一種文化上下文下的所有文字元號集合,它的作用是規定了某個文化下的所有字元,以及該字元在資訊交換系統下的表示方式,在計算機資訊系統下是位元組或01序列。
對於java web應用,狹隘的編碼解碼的過程可以簡單的理解為:編碼的過程是文字字串資訊編碼成01序列,解碼是將01序列恢復為文字字串資訊,具體編碼成什麼樣的01序列是由編碼採用的字符集來決定的,也就是編碼方案。
亂碼是對資訊採用的編碼方案無法理解,使用了錯誤的編碼方案對資訊進行解碼造成的。如果要理解一段資訊的真實意圖,就得知道資訊採用的編碼方案,這是資訊交換的金鑰,這就是為什麼戰爭年代破解對方電報加密方式,實際上就是在破譯對方的編碼方案。
【2】瀏覽器端的編碼
① http協議層的編碼解碼
http協議層的字符集關係到http傳送者和接送者採用什麼字符集方案解析對方傳送的內容。
② 瀏覽器端的編碼
請求端常規請求方式主要為form、url、ajax、http元件如HttpClient API。
瀏覽器存在文件編碼方案charset的概念,文件的編碼方案等同於文件解碼方案,它對文件中發生的請求編碼會產生影響。
影響form提交資料的編碼的因素包括:form的accept-charset屬性、html文件的編碼方案即document.charset。其中,form的accept-charset是否能夠有效,依賴具體瀏覽器的實現,有些瀏覽器並不支援,如IE。文件編碼方案可以通過document.charset來修改。
文件內的url編碼,如iframe的src指定的url,以文件編碼方案為準,位址列的url的編碼方案完全取決於具體的瀏覽器實現,通過HttpClient元件傳送請求時,url是能任意指定編碼方案的。
ajax傳送http請求的url編碼方式完全取決於瀏覽器實現,一般支援以文件編碼方案來決定,但是資料體統一採用utf-8,另外,雖然ajax可以指定header在content-type說明編碼方案,但這種做法不會對url、資料體的編碼方案產生任何影響,甚至在有些瀏覽器中,最終content-type中的編碼描述都無法真正影響。
另外,header的預設編碼方案是iso-8859-1,這個是http規範。
【3】服務端的解碼
服務端的httpserver需要解碼的物件包括:header、url、資料體。
-
header解碼方案是iso-8859-1。
-
url解碼方案通常稱為URIEncoding,一般HttpServer會提供相應設定,標準servlet並不提供該介面。jetty預設utf-8字符集來解碼,但其他httpserver如tomcat會預設iso-8859-1。
-
資料體解碼在servlet中可以通過request.setCharacterEncoding來設定。一般的,有些httpserver會以characterEncoding>request請求頭字符集>utf-8的優先順序來決定資料體的解碼方案。
web伺服器接收到客戶端的請求後,會將其內容轉給web容器來處理;
因為接到的請求path(url)是編碼過的二進位制流,所以在處理前會將其轉換成ASCII碼 。但是請求中可能還有部分引數和訊息體的資料是經過編碼的(例如中文字元被編碼),這裡就涉及到對請求內容和引數進行解碼的問題。
Servlet伺服器實現的Servlet遇到URL和POST提交的資料中含有%的字串,它會按照指定的字符集解碼。
下面兩個Servlet方法返回的結果都是經過解碼的:
request.getParameter("name");
request.getPathInfo();
這裡所說的"指定的字符集"是在應用伺服器的配置檔案中配置。預設為ISO-8859-1。
【4】服務端的編碼
服務端解碼是針對請求而言,服務端編碼是針對響應而言。服務端httpserver需要編碼的物件是:header、資料體。
header的編碼方案同樣是iso-8859-1。
通常情況下,服務端必須要指定返回資料體的編碼方案且要在header中標註編碼方案,否則httpserver一般預設iso-8859-1對輸出進行編碼,而瀏覽器也無法得知返回資料體的編碼方案,只能自行猜測,完全依賴瀏覽器自己的實現。
response.setCharacterEncoding
的職能是告訴httpserver資料體的編碼方案,並不會也不應該影響到header中的編碼方案的標註。
response.setContentType
會影響到header的編碼方案的標註,瀏覽器根據該標識決定解碼方案。
對於一個健全的httpserver來說,在同時通過兩個方法指定了資料體編碼方案和header編碼方案標註的情況下,資料體編碼方案應該由後者決定,這樣使瀏覽器端得到的編碼資訊和服務端真正編碼資訊一致。
另外,一定要注意的是這兩個指定編碼方案的方法必須在response建立輸出流之前呼叫,輸出流一旦建立,編碼方案無法後期指定。
【5】瀏覽器端的解碼
瀏覽器端的解碼針對的是伺服器返回的響應。瀏覽器端對返回進行解碼的物件包括:header、資料體:
-
header的解碼方案是iso-8859-1。
-
瀏覽器的資料體解碼方案依賴返回資訊,瀏覽器首先從返回頭header中查詢編碼方案標註,如果沒有標註,在得知返回內容為html內容的話,將從head的meta標籤中讀取,如果還沒找到,瀏覽器就不知道如何解碼,會消極的選擇一種解碼方案。
在理論上,推薦html文件在meta中宣告編碼,且編碼的宣告一定要在檔案開始的1024位元組內完成,所以最好在head標籤開始時立即宣告。
文件中通常都會有一些通過url下載的資原始檔,如css和js檔案,如果資原始檔輸出時沒有在返回頭中指定明確的編碼方案,瀏覽器無法得知編碼方案,只能以上面介紹到的文件編碼方案來進行解碼,這也是瀏覽器容錯的最佳策略。
瀏覽器根據http頭中的ContentType("text/html; charset=GBK")
,指定的字符集來解碼伺服器傳送過來的位元組流。
我們可以呼叫 HttpServletResponse.setContentType()設定http頭的ContentType。
需要注意的是,URL中的PathInfo和QueryString字串的編碼和解碼是由瀏覽器和應用伺服器的配置決定的。我們的程式不能設定,也不要期望用request.setCharacterEncoding()
方法能設定URL中引數值解碼時的字符集(該方法只針對post)。通常我們也無需關注這個,我們更應該關注訊息體的編碼解碼。
如,可能會在Tomcat中這樣配置:
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000" URIEncoding="UTF-8"
redirectPort="8443" />
【6】開發人員必須清楚的servlet規範
(1) HttpServletRequest.setCharacterEncoding()
方法 僅僅只適用於設定post提交的request body的編碼而不是設定get方法提交的queryString的編碼。
該方法告訴應用伺服器應該採用什麼編碼解析post傳過來的內容。
(2) HttpServletRequest.getPathInfo()
返回的結果是由Servlet伺服器解碼(decode)過的。
(3) HttpServletRequest.getRequestURI()
返回的字串沒有被Servlet伺服器decoded過。
(4) POST提交的資料是作為request body的一部分。
(5) 網頁的Http頭中ContentType("text/html; charset=GBK")
的作用:
- (a) 告訴瀏覽器網頁中資料是什麼編碼;
- (b) 表單提交時,通常瀏覽器會根據ContentType指定的charset對錶單中的資料編碼,然後傳送給伺服器的。
這裡需要注意的是:這裡所說的ContentType是指http頭的ContentType,而不是在網頁中meta中的ContentType。
(6) JSP頁面 pageEncoding作用
如下所示:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
pageEncoding作用如下:
response.setContentType("text/html; charset=UTF-8");