一、字符集與文字編碼簡介
1. 計算機如何顯示文字
我們知道,計算機是以二進制的“形式”來保存和處理數據的,也就是說,不管我們使用鍵盤進行輸入,還是讓計算機去讀取一個文本文件,計算機得到的原始內容是一些二進制序列,當需要對這些二進制序列進行顯示時,計算機會依照某種“翻譯機制”(也就是編碼方式),取到這些二進制序列所表示的每個文字的“輪廓描述”(點陣或者矢量圖),知道了輪廓,計算機便可以將二進制序列所表示的實際的文字形狀顯示到屏幕上了,這里面的思想和用學號來表示一個學生是一樣的。(當然,這里面具體的知識點會很多,相關知識可以參考《計算機圖形學》中顯示原理的部分和其他與計算機顯示原理相關的基礎書籍)。
2. 字符集
將一些自然語言中的字符組成一個集合,並對集合中的每個字符制定規范化的編碼方式,這個字符的集合和規范化的編碼方式就組成了一個字符集。如ASCII字符集里面包括了所有的英文字母,並且指定了這些英文字母的編碼規則。GB2312字符集里面包括了常用的簡體漢字,並且指定了這些簡體漢字的編碼規則。
3. 字符編碼
字符編碼,就是建立一套自然語言中的“字符”跟計算機能夠存儲處理的二進制數的映射的規則,即在一個字符集內,用一個特定的二進制數表示一個唯一“字符”,類似於學號跟學生的映射關系。為了保證統一性,兼容性,國際上會對“字符”和“內碼”的映射關系指定標准,這樣就有了ASCII、Unicode等標准的編碼方式,詳細的編碼方式請參考字符編碼和其他相關資料。
二、泛意義上的編碼和解碼
1. 為什么需要編碼
當數據不利於處理、存儲的時候,就需要對它們進行編碼。如對字符進行編碼是因為自然語言中的字符不利於計算機處理和存儲。對圖片信息、視頻信息、聲音信息進行壓縮、優化,將其“格式化”,是為了在保證媒體資源質量的同時,盡量的節省網絡帶寬和本地存儲的空間。對URL進行編碼,是為了避免URL解析發生歧義,簡化解碼方式,如:URL采用“&”作為不同參數的分隔符,假如某個特定的參數的名稱或者值本身就包括分隔符“&”,如果不將參數中的“&”做編碼轉換,那勢必會增加URL解析的復雜性,提高解析錯誤的概率。
2. 怎么樣進行編碼和解碼
根據實際需求的差異,編碼、解碼算法有可能會很復雜,也有可能非常的簡單,但是從根本上來講,編碼、解碼只是在做翻譯工作,將一種形式的數據翻譯為另一種形式的數據,如,最簡單的編碼、解碼就是相當於從一個Map中根據key查找value,然后使用value代替實際數據中的key的值。復雜一點兒的編碼如javascript中的encodeURIComponent和decodeURIComponent,encodeURIComponent負責將字符串中不符合URL編碼規范的字符轉換為“%”形式的十六進制Unicode內碼序列,decodeURIComponent負責將“%”形式的十六進制Unicode內碼序列轉換為實際的字符。
三、HTTP協議中的編碼和解碼
1. URL的編碼和解碼
首先,由於URL是采用ASCII字符集進行編碼的,所以如果URL中含有非ASCII字符集中的字符,那就需要對其進行編碼。再者,由於URL中好多字符是保留字,他們在URL中具有特殊的含義。如“&”表示參數分隔符,如果想要在URL中使用這些保留字,那就得對他們進行編碼。
根據2005年發布的RFC3986“%編碼”規范:對URL中屬於ASCII字符集的非保留字不做編碼;對URL中的保留字需要取其ASCII內碼,然后加上“%”前綴將該字符進行替換(編碼);對於URL中的非ASCII字符需要取其Unicode內碼,然后加上“%”前綴將該字符進行替換(編碼)。由於這種編碼是采用“%”加上字符內碼的方式,所以,有些地方也稱其為“百分號編碼”。
雖然“百分號編碼”對URL的編碼方式做了詳細的規定,但是實踐中,瀏覽器對於URL的編碼方式還是存在一些差異(主要表現在對非ASCII字符編碼的差異),接下來我們首先展示不同瀏覽器(chrome和IE)對URL編碼
的差異性,然后再對這些差異性做一些客觀的總結和分析。
1) 對URL中的非ASCII字符的編碼,原始URL地址:http://test/wangfengpaopao/王豐,請求方式為直接在瀏覽器地址欄輸入地址,發起請求。
a) chrome
b) IE
可以看出對於路徑中的非ASCII字符,chrome和IE都是按照RFC3986“%編碼”進行編碼的,取的是非ASCII字符的Unicode內碼。
2) 對URL參數中的非ASCII字符的編碼,原始URL地址:http://test/wangfengpaopao/王豐?name=王豐,請求方式為直接在瀏覽器地址欄輸入地址,發起請求。
a) chrome和IE11
b) IE11以下版本(使用gbk進行解碼)
可以看出對於查詢參數中的非ASCII字符,chrome及其IE11都是按照都是按照RFC3986“%編碼”進行編碼的,取的是非ASCII字符的Unicode內碼。IE11以下版本直接發送的是非ASCII字符相對應的當前系統默認編碼的內碼。
3) 對表單字段name的值中的非ASCII字符的編碼,請求地址:http://test/wangfengpaopao/王豐,請求方式為get。
a) Chrome
i. 頁面gbk編碼
ii. 頁面utf-8編碼
b) IE
i. 頁面gbk編碼
ii. 頁面utf-8編碼
可以看出當通過表單發送get請求時,對於表單字段內容中的非ASCII,chrome和IE都會采用當前頁面的編碼對其進行“百分號”編碼。
4) 對表單字段name的值中的非ASCII字符的編碼,請求地址:http://test/wangfengpaopao/王豐,請求方式為post,enctype為application/x-www-form-urlencoded。
a) chrome
i. 頁面UTF-8編碼
ii. 頁面GBK編碼
b) IE
i. 頁面UTF-8編碼
ii. 頁面GBK編碼
可以看出當通過表單發送post請求時,對於表單字段內容中的非ASCII,chrome和IE都會采用當前頁面的編碼對其進行“百分號”編碼。
5) 對URL中的非ASCII字符的編碼,原始URL地址:http://test/wangfengpaopao/王豐?name=王豐,請求方式為發起AJAX請求,method為get。
a) Chrome
i. 頁面utf-8編碼
ii. 頁面gbk編碼
b) IE
i. 頁面utf-8編碼
1. IE6(gbk解碼)
2. IE11
ii. 頁面GBK編碼
1. IE6(gbk解碼)
2. IE11
可以看出,對於URL中的非ASCII字符,ie6沒有做任何的編碼工作,而chrome和IE11則是按照用表單get請求的方式對URL進行編碼的。
6) 對URL中的非ASCII字符的編碼,原始URL地址:http://test/wangfengpaopao/王豐,請求方式為發起AJAX請求,method為post,數據位name=王豐,content-type為application/x-www-form-urlencoded。
a) Chrome
i. 頁面utf-8編碼
ii. 頁面gbk編碼
b) IE
i. 頁面utf-8編碼
1. IE6
2. IE11
ii. 頁面GBK編碼
1. IE6
2. IE11
可以看出,對於使用post發送並且content-type為application/x-www-form-urlencoded的請求,各個瀏覽器都沒有對數據進行“百分號”編碼。
從上面的實驗結果我們看得出:
① 對於URL中的路徑部分,IE和chrome都會統一采用utf-8編碼對URL中的非ASCII字符進行百分號編碼。
② 對於新開頁面,IE11以下版本不會對URL中的參數部分做編碼,chrome和IE11會采用utf-8編碼對URL中的非ASCII字符進行百分號編碼。
③ 對於通過表單發起的請求(不管是post還是get方式),IE和chrome都會采用當前頁面的默認編碼對URL參數中的非ASCII字符進行百分號編碼。
④ 對於AJAX通過get方式發起的請求,IE11和chrome會根據當前頁面的默認編碼對URL參數中的非ASCII字符進行百分號編碼。而IE6不會對URL非ASCII表示的路徑信息和參數信息進行編碼。
⑤ 對於AJAX通過post方式發起的請求,即便設定了application/x-www-form-urlencoded頭信息,瀏覽器也不會對數據做任何的編碼(或者說瀏覽器不把發送的數據當做URL的一部分)。
不同的瀏覽器在不同情況下處理URL中非ASCII字符的編碼方式可謂是千差萬別,好在瀏覽器對表單數據的編碼處理是一致的,即:對URL路徑中的非ASCII字符采用UTF-8字符集進行百分號編碼;對中的表單數據(包括post時enctype為application/x-www-form-urlencoded),采用頁面默認的編碼字符集進行百分號編碼。
對於AJAX發起請求時URL編碼的差異性,我們可以對URL或者數據中的非ASCII字符使用javascript的encodeURIComponent進行編碼,統一編碼方式,簡化服務器解碼的復雜度。
2. 瀏覽器對不同媒體資源類型(mime-type)的“資源”的解碼
1) 與資源類型和編碼類型相關的HTTP頭信息。
a) 瀏覽器request時攜帶的頭信息
b) 服務器response時攜帶的頭信息
當通過瀏覽器發起一個資源請求,瀏覽器會攜帶Accept頭信息,標識瀏覽器需要的mime-type,並且指定瀏覽器對不同mime-type的喜好系數q,同時瀏覽器也會發送Accept-Charset頭信息,標識瀏覽器字符集解碼類型。
當服務器返回“符合”瀏覽器需求的資源時,服務器也會攜帶Content-Type頭信息,標識返回資源的媒體類型和編碼方式。
2) 瀏覽器對不同媒體資源類型的資源的解析過程
不管怎樣,瀏覽器發起一個請求時所攜帶的mime-type信息只是對服務器返回資源的一種“期望”,資源本身的mime-type還得用服務器應答時攜帶的mime-type信息進行表示。
籠統的講,對於文本類型的數據(html/css/js/xml等),瀏覽器首先會根據資源的Charset,將文本流的編碼轉換為與資源的mime-type相對應的解碼器處理數據時所使用的編碼,比如對於javascript文件,如果文本流本身 是GBK編碼的,那就得首先轉為Unicode編碼,然后再交給javascript引擎去解析執行。當然對於不同類型的資源,瀏覽器進行解碼的流程也是不一樣的,下面會用流程圖較詳細的說明下瀏覽器對於HTML、CSS、Javascript的通用的解碼步驟。
① 瀏覽器對於HTML文檔的解碼流程
② 瀏覽器對於CSS/JS文檔的解碼流程
3) AJAX請求中,瀏覽器對“數據”的解碼流程
AJAX請求跟頁面內的有資源屬性source的標簽發起的請求有所不同,從本質來說,AJAX請求得到的數據,瀏覽器都認為它是普通的文本流,跟具體的mime-type無關,即便是responseXML這樣的數據,它也是由瀏覽器對responseText進行了XML解析,這與使用javascript對responseText進行XML解析的道理是一樣的。
AJAX解碼的流程
3. 服務器對資源的編碼和解碼
弄清了編碼和解碼的原理以及瀏覽器的編解碼流程,服務器的編碼和解碼可說的已經不多了,將來兵擋,水來土掩而已。要說的是為了避免URL解碼時的各種兼容性問題,最好有統一的規范,比如通過接口提交的數據中的非ASCII字符都是用encodeURIComponent進行URL編碼。為了避免瀏覽器對資源進行解碼的兼容性,服務器返回資源時,明確正確的指定Charset信息也是很重要的。
除了熟練使用自己所擅長的編程語言以及相對應的框架提供的編解碼相關的方法外,可以參考iconv了解具體的編碼轉換原理,參考UCharsetDetector了解字符集探測原理。