1. 程式人生 > >Web應用中瀏覽器與服務端的編碼和解碼

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");