一、問(wèn)題的由來(lái)
URL就是網(wǎng)址逸邦,只要上網(wǎng)恩沛,就一定會(huì)用到。
一般來(lái)說(shuō)缕减,URL只能使用英文字母雷客、阿拉伯?dāng)?shù)字和某些標(biāo)點(diǎn)符號(hào),不能使用其他文字和符號(hào)桥狡。比如搅裙,世界上有英文字母的網(wǎng)址"http://www.abc.com",但是沒(méi)有希臘字母的網(wǎng)址"http://www.aβγ.com"(讀作阿爾法-貝塔-伽瑪.com)裹芝。這是因?yàn)榫W(wǎng)絡(luò)標(biāo)準(zhǔn)RFC 1738做了硬性規(guī)定:
"...Only alphanumerics [0-9a-zA-Z], the special characters "$-_.+!*'()," [not including the quotes - ed], and reserved characters used for their reserved purposes may be used unencoded within a URL."
"只有字母和數(shù)字[0-9a-zA-Z]部逮、一些特殊符號(hào)"$-_.+!*'(),"[不包括雙引號(hào)]、以及某些保留字嫂易,才可以不經(jīng)過(guò)編碼直接用于URL兄朋。"
這意味著,如果URL中有漢字怜械,就必須編碼后使用颅和。但是麻煩的是,RFC 1738沒(méi)有規(guī)定具體的編碼方法缕允,而是交給應(yīng)用程序(瀏覽器)自己決定融虽。這導(dǎo)致"URL編碼"成為了一個(gè)混亂的領(lǐng)域。
下面就讓我們看看灼芭,"URL編碼"到底有多混亂。我會(huì)依次分析四種不同的情況般又,在每一種情況中彼绷,瀏覽器的URL編碼方法都不一樣巍佑。把它們的差異解釋清楚之后,我再說(shuō)如何用Javascript找到一個(gè)統(tǒng)一的編碼方法寄悯。
二萤衰、情況1:網(wǎng)址路徑中包含漢字
打開(kāi)IE(我用的是8.0版),輸入網(wǎng)址"http://zh.wikipedia.org/wiki/春節(jié)"猜旬。注意脆栋,"春節(jié)"這兩個(gè)字此時(shí)是網(wǎng)址路徑的一部分。
查看HTTP請(qǐng)求的頭信息洒擦,會(huì)發(fā)現(xiàn)IE實(shí)際查詢(xún)的網(wǎng)址是"http://zh.wikipedia.org/wiki/%E6%98%A5%E8%8A%82"椿争。也就是說(shuō),IE自動(dòng)將"春節(jié)"編碼成了"%E6%98%A5%E8%8A%82"熟嫩。
我們知道秦踪,"春"和"節(jié)"的utf-8編碼分別是"E6 98 A5"和"E8 8A 82",因此掸茅,"%E6%98%A5%E8%8A%82"就是按照順序椅邓,在每個(gè)字節(jié)前加上%而得到的。(具體的轉(zhuǎn)碼方法昧狮,請(qǐng)參考我寫(xiě)的《字符編碼筆記》景馁。)
在Firefox中測(cè)試,也得到了同樣的結(jié)果逗鸣。所以合住,結(jié)論1就是,網(wǎng)址路徑的編碼慕购,用的是utf-8編碼聊疲。
三、情況2:查詢(xún)字符串包含漢字
在IE中輸入網(wǎng)址"http://www.baidu.com/s?wd=春節(jié)"沪悲。注意获洲,"春節(jié)"這兩個(gè)字此時(shí)屬于查詢(xún)字符串,不屬于網(wǎng)址路徑殿如,不要與情況1混淆贡珊。
查看HTTP請(qǐng)求的頭信息,會(huì)發(fā)現(xiàn)IE將"春節(jié)"轉(zhuǎn)化成了一個(gè)亂碼涉馁。
切換到十六進(jìn)制方式门岔,才能清楚地看到,"春節(jié)"被轉(zhuǎn)成了"B4 BA BD DA"烤送。
我們知道蟆豫,"春"和"節(jié)"的GB2312編碼(我的操作系統(tǒng)"Windows XP"中文版的默認(rèn)編碼)分別是"B4 BA"和"BD DA"康震。因此澎现,IE實(shí)際上就是將查詢(xún)字符串朱躺,以GB2312編碼的格式發(fā)送出去。
Firefox的處理方法,略有不同。它發(fā)送的HTTP Head是"wd=%B4%BA%BD%DA"。也就是說(shuō)纫普,同樣采用GB2312編碼,但是在每個(gè)字節(jié)前加上了%好渠。
所以昨稼,結(jié)論2就是,查詢(xún)字符串的編碼拳锚,用的是操作系統(tǒng)的默認(rèn)編碼假栓。
四、情況3:Get方法生成的URL包含漢字
前面說(shuō)的是直接輸入網(wǎng)址的情況晌畅,但是更常見(jiàn)的情況是但指,在已打開(kāi)的網(wǎng)頁(yè)上,直接用Get或Post方法發(fā)出HTTP請(qǐng)求抗楔。
根據(jù)臺(tái)灣中興大學(xué)呂瑞麟老師的試驗(yàn)棋凳,這時(shí)的編碼方法由網(wǎng)頁(yè)的編碼決定,也就是由HTML源碼中字符集的設(shè)定決定连躏。
<meta http-equiv="Content-Type" content="text/html;charset=xxxx">
如果上面這一行最后的charset是UTF-8剩岳,則URL就以UTF-8編碼;如果是GB2312入热,URL就以GB2312編碼拍棕。
舉例來(lái)說(shuō),百度是GB2312編碼勺良,Google是UTF-8編碼绰播。因此,從它們的搜索框中搜索同一個(gè)詞"春節(jié)"尚困,生成的查詢(xún)字符串是不一樣的蠢箩。
百度生成的是%B4%BA%BD%DA,這是GB2312編碼事甜。
Google生成的是%E6%98%A5%E8%8A%82谬泌,這是UTF-8編碼。
所以逻谦,結(jié)論3就是掌实,GET和POST方法的編碼,用的是網(wǎng)頁(yè)的編碼邦马。
五贱鼻、情況4:Ajax調(diào)用的URL包含漢字
前面三種情況都是由瀏覽器發(fā)出HTTP請(qǐng)求宴卖,最后一種情況則是由Javascript生成HTTP請(qǐng)求,也就是Ajax調(diào)用忱嘹。還是根據(jù)呂瑞麟老師的文章嘱腥,在這種情況下,IE和Firefox的處理方式完全不一樣拘悦。
舉例來(lái)說(shuō),有這樣兩行代碼:
url = url + "?q=" +document.myform.elements[0].value; // 假定用戶(hù)在表單中提交的值是"春節(jié)"這兩個(gè)字
http_request.open('GET', url, true);
那么橱脸,無(wú)論網(wǎng)頁(yè)使用什么字符集础米,IE傳送給服務(wù)器的總是"q=%B4%BA%BD%DA",而Firefox傳送給服務(wù)器的總是"q=%E6%98%A5%E8%8A%82"添诉。也就是說(shuō)屁桑,在Ajax調(diào)用中,IE總是采用GB2312編碼(操作系統(tǒng)的默認(rèn)編碼)栏赴,而Firefox總是采用utf-8編碼蘑斧。這就是我們的結(jié)論4。
六须眷、Javascript函數(shù):escape()
好了竖瘾,到此為止,四種情況都說(shuō)完了花颗。
假定前面你都看懂了捕传,那么此時(shí)你應(yīng)該會(huì)感到很頭痛。因?yàn)槔┤埃瑢?shí)在太混亂了庸论。不同的操作系統(tǒng)、不同的瀏覽器棒呛、不同的網(wǎng)頁(yè)字符集聂示,將導(dǎo)致完全不同的編碼結(jié)果。如果程序員要把每一種結(jié)果都考慮進(jìn)去簇秒,是不是太恐怖了鱼喉?有沒(méi)有辦法,能夠保證客戶(hù)端只用一種編碼方法向服務(wù)器發(fā)出請(qǐng)求宰睡?
回答是有的蒲凶,就是使用Javascript先對(duì)URL編碼,然后再向服務(wù)器提交拆内,不要給瀏覽器插手的機(jī)會(huì)旋圆。因?yàn)镴avascript的輸出總是一致的,所以就保證了服務(wù)器得到的數(shù)據(jù)是格式統(tǒng)一的麸恍。
Javascript語(yǔ)言用于編碼的函數(shù)灵巧,一共有三個(gè)搀矫,最古老的一個(gè)就是escape()。雖然這個(gè)函數(shù)現(xiàn)在已經(jīng)不提倡使用了刻肄,但是由于歷史原因瓤球,很多地方還在使用它,所以有必要先從它講起敏弃。
實(shí)際上卦羡,escape()不能直接用于URL編碼,它的真正作用是返回一個(gè)字符的Unicode編碼值麦到。比如"春節(jié)"的返回結(jié)果是%u6625%u8282绿饵,也就是說(shuō)在Unicode字符集中,"春"是第6625個(gè)(十六進(jìn)制)字符瓶颠,"節(jié)"是第8282個(gè)(十六進(jìn)制)字符拟赊。
它的具體規(guī)則是,除了ASCII字母粹淋、數(shù)字吸祟、標(biāo)點(diǎn)符號(hào)"@ * _ + - . /"以外,對(duì)其他所有字符進(jìn)行編碼桃移。在\u0000到\u00ff之間的符號(hào)被轉(zhuǎn)成%xx的形式屋匕,其余符號(hào)被轉(zhuǎn)成%uxxxx的形式。對(duì)應(yīng)的解碼函數(shù)是unescape()谴轮。
所以炒瘟,"Hello World"的escape()編碼就是"Hello%20World"。因?yàn)榭崭竦腢nicode值是20(十六進(jìn)制)第步。
還有兩個(gè)地方需要注意疮装。
首先,無(wú)論網(wǎng)頁(yè)的原始編碼是什么粘都,一旦被Javascript編碼廓推,就都變?yōu)閡nicode字符。也就是說(shuō)翩隧,Javascipt函數(shù)的輸入和輸出樊展,默認(rèn)都是Unicode字符。這一點(diǎn)對(duì)下面兩個(gè)函數(shù)也適用堆生。
其次专缠,escape()不對(duì)"+"編碼。但是我們知道淑仆,網(wǎng)頁(yè)在提交表單的時(shí)候涝婉,如果有空格,則會(huì)被轉(zhuǎn)化為+字符蔗怠。服務(wù)器處理數(shù)據(jù)的時(shí)候墩弯,會(huì)把+號(hào)處理成空格吩跋。所以,使用的時(shí)候要小心渔工。
七锌钮、Javascript函數(shù):encodeURI()
encodeURI()是Javascript中真正用來(lái)對(duì)URL編碼的函數(shù)。
它著眼于對(duì)整個(gè)URL進(jìn)行編碼引矩,因此除了常見(jiàn)的符號(hào)以外梁丘,對(duì)其他一些在網(wǎng)址中有特殊含義的符號(hào)"; / ? : @ & = + $ , #",也不進(jìn)行編碼旺韭。編碼后兰吟,它輸出符號(hào)的utf-8形式,并且在每個(gè)字節(jié)前加上%茂翔。
它對(duì)應(yīng)的解碼函數(shù)是decodeURI()。
需要注意的是履腋,它不對(duì)單引號(hào)'編碼珊燎。
八、Javascript函數(shù):encodeURIComponent()
最后一個(gè)Javascript編碼函數(shù)是encodeURIComponent()遵湖。與encodeURI()的區(qū)別是悔政,它用于對(duì)URL的組成部分進(jìn)行個(gè)別編碼,而不用于對(duì)整個(gè)URL進(jìn)行編碼延旧。
因此谋国,"; / ? : @ & = + $ , #",這些在encodeURI()中不被編碼的符號(hào)迁沫,在encodeURIComponent()中統(tǒng)統(tǒng)會(huì)被編碼芦瘾。至于具體的編碼方法,兩者是一樣集畅。
它對(duì)應(yīng)的解碼函數(shù)是decodeURIComponent()近弟。
(完)