1. 程式人生 > >URL中帶加號的處理

URL中帶加號的處理

問題解決 -c 性問題 www. 成功 window 規範 %20 保留字

問題起因:
客戶訂購了一關鍵字為"e+h 變送器" , 在首頁推薦廣告中,會根據用戶在search 搜索過的關鍵字進行一個匹配投放。技術實現是UED 通過JS 獲取cookie 中的h_keys 內容,拼裝到 http://xxxxx/advert/ctp_advert.htm?num=4&keyword= {keyword} 。 這裏取出來對應的cookie 信息為中文,最後通過一個ajax 發起一個GET請求。

所以針對最後的請求是:http://xxxxxx/advert/ctp_advert.htm?num= 4&keyword=e+h 變送器。 而在服務端接受到對應的請求參數時,發現參數為:e h 變送器, + 號沒了。 初步懷疑跟URL規範相關,需要進行url encode。


問題分析:

查了下JS encode 的相關內容, 總於發現+ 號的秘密。
html 中因為一些非標準的做法,將+ 等同於空格進行處理 (當Html 的表單被提交時, 每個表單域都會被Url 編碼之後才在被發送。由於歷史的原因,表單使用的Url 編碼實現並不符合最新的標準。例如對於空格使用 的編碼並不是%20 ,而是+ 號,如果表單使用的是Post 方法提交的,我們可以在HTTP 頭中看到有一個Content-Type 的header ,值為 application/x-www-form-urlencoded ,大部分應用程序均能處理這種非標準實現的Url 編碼)。
在搜索引擎中做了下嘗試: 

keyword = e h 變送器 , url = http: //www.google.cn/search?hl=zh-CN&newwindow=1&q=e+h變送器 ( 空格被轉化為+ 號)
keyword = e+ h 變送器 , url = http: //www.google.cn/search?hl=zh-CN&newwindow=1&q=e%2Bh變送器 (+ 號被進行了轉義為%2B ,程序才能正常處理)


問題解決:

思路1:
1. 要想正常傳輸+ 號而不被轉義為空格,需要進行進行編碼為%2B 。查了下幾個編碼函數,發現只有encodeURIComponent 才會對+ 號進行編碼處理。

2. encodeURIComponent 默認為采用UTF-8 字符集,理論上只需要在原先的請求中添加_input_charset=utf-8(由 pipeline 中的SetLocaleValve 進行解析) ,就可以得到正確的 e+h 變送器。

在實施過程中,發現結果並不是預期的那樣。 客戶端通過js encode 後,在服務端解析後一直是亂碼。 查了下byte ,發現服務端一直是用GBK 在進行解析, 針對變送器的UTF-8 編碼的byte 為{-27,-113,-104,-23,-128,-127},客戶端用GBK 解析後變為{-27.-113.- 104.-23,-63,-63} ,針對最後兩byte 因為字符不可見,導致全部被替換為-63 。網上查了下,針對 utf-8 -> gbk -> utf-8 在一定情況下就會出現該問題(http://lingqi1818.iteye.com/blog/348953 ) 。


思路2 :
繼續追查對應的_input_charset=utf-8 未生效的原因,DEBUG 看到在SetLocaleValve 中的確設置了request.setCharsetEncoding 為utf-8 。初步懷疑是否跟jboss server 的配置有關,查了下跟URIEncoding 和useBodyEncodingForURI 設置有關。 目前公司所使用的jboss 為4.05 ,對應俄tomact 配置中只指定了對應的URIEncoding=GBK 。正因為這樣,導致設置的_input_charset 針對GBK 的提交沒有效果 ,還是按照GBK 進行解析。

1. 考慮將請求由GET 換成POST , 這樣就可以使用_input_charset

但在實施過程中,和UED 溝通過程,針對POST 的會引起一個跨域請求的問題。此方案又只能做罷


思路3 ( 實踐成功) :

1. UED 進行偽url encode 的實現 , 將+ 號進行%2B 的編碼。 因為目前JS 中沒有現成的函數,這裏只是通過replace(/\+/g, ‘%2B‘) 進行了轉化。


總 結

針對+ 號的處理,針對不同的業務場景需要不同的處理方案,描述下幾種場景:
1. 非Ajax 請求
可以直接使用Form 表單的 GET ,POST 的urlencode 協議,自動實現+ => %2B 的轉化
2. Ajax 請求
* GET 請求 : 很無奈,只能使用方案3 ,人為進行+ 號轉化。
* POST 請求( 同一應用,非跨域請 求) : 使用encodeURIComponent + _input_charset=utf-8 指定編碼進行處理。

ps: 前面提的這幾種方案,都是基於+ 號是正常的業務場景進行考慮。同時我們也可以從業務層面進行一個梳理,+ 號處理是否有其必要性,能從業務數據入口直接規避 那就最好了。


背景知識:


URIEncoding 和useBodyEncodingForURI

對於URL 提交的數據和表單中GET 方式提交的數據,在接收數據的JSP 中設置request.setCharacterEncoding參數是不行的, 因為在Tomcat5.0 中,默認情況下使用ISO- 8859-1 對URL 提交的數據和表單中GET 方式提交的數據進行重新編碼(解碼),而不使用該參數對URL 提交的數據和表單中GET 方式提交的數據進行 重新編碼(解碼)。要解決該問題,應該在Tomcat 的配置文件的Connector 標簽中設置useBodyEncodingForURI 或者URIEncoding 屬性,其中useBodyEncodingForURI 參數表示是否用 request.setCharacterEncoding 參數對URL提交的數據和表單中GET 方式提交的數據進行重新編碼 ,在默認情 況下,該參數為false (Tomcat4.0 中該參數默認為true ); URIEncoding 參數 指定對所有GET 方式請求(包括URL 提交的數據和表單中GET 方式提交的數據)進行統一的重新編碼(解碼)的編碼 。 URIEncoding 和useBodyEncodingForURI 區別是,URIEncoding 是對所有GET 方式的請求的數據進行統一的重新編碼 (解碼),而useBodyEncodingForURI 則是根據響應該請求的頁面的request.setCharacterEncoding 參數對數 據進行的重新編碼(解碼),不同的頁面可以有不同的重新編碼(解碼)的編碼。所以對於URL 提交的數據和表單中GET 方式提交的數據,可以修改 URIEncoding 參數為瀏覽器編碼或者修改useBodyEncodingForURI 為true ,並且在獲得數據的JSP 頁面中request.setCharacterEncoding 參數設置成瀏覽器編碼。


為什麽需要Url 編碼
1. Url 中有些字符會引起歧義 , =,& 號等
2. Url 的編碼格式采用的是ASCII 碼,而不是Unicode ,這也就是說你不能在Url 中包含任何非ASCII 字符,例如中文


哪些字符需要編碼
RFC3986 文檔規定,Url 中只允許包含英文字母(a-zA-Z )、數字(0-9 )、-_.~4 個特殊字符以及所有保留字符。
Url 可以劃分成若幹個組件,協議、主機、路徑等。RFC3986 中指定了以下字符為保留字符: ! * ‘ ( ) ; : @ & = + $ , / ? # [ ]


如何對Url 中的非法字符進行編碼
Url 編碼通常也被稱為百分號編碼(Url Encoding ,also known as percent-encoding ),是因為它的編碼方式非常簡單,使用% 百分號加上兩位的字符——0123456789ABCDEF—— 代表一個字節的 十六進制形式。Url 編碼默認使用的字符集是US-ASCII 。例如a 在US-ASCII 碼中對應的字節是0x61 ,那麽Url 編碼之後得到的就是% 61 ,我們在地址欄上輸入http: //g.cn/search?q=%61%62%63,實際上就等同於在google 上搜索abc 了。又如@ 符號在 ASCII 字符集中對應的字節為0x40 ,經過Url 編碼之後得到的是%40 。



Javascript 中的escape,encodeURI 和encodeURIComponent 的區別

Javascript 中提供了3 對函數用來對Url 編碼以得到合法的Url ,它們分別是escape / unescape,encodeURI / decodeURI 和encodeURIComponent / decodeURIComponent 。解碼和編碼的過程是可逆的.

兼容性不同
escape 函數是從Javascript1.0 的時候就存在了,其他兩個函數是在Javascript1.5 才引入的。但是由於Javascript1.5 已經非常普及了,所以實際上使用encodeURI 和encodeURIComponent 並不會有什麽兼容性問題。

對Unicode 字符的編碼方式不同
這三個函數對於ASCII 字符的編碼方式相同,均是使用百分號+ 兩位十六進制字符來表示。但是對於Unicode 字符,escape 的編碼方式是% uxxxx ,其中的xxxx 是用來表示unicode 字符的4 位十六進制字符。這種方式已經被W3C 廢棄了。但是在ECMA-262 標準中仍然保留著 escape 的這種編碼語法。encodeURI 和encodeURIComponent 則使用UTF-8 對非ASCII 字符進行編碼,然後再進行百分號 編碼。這是RFC 推薦的。因此建議盡可能的使用這兩個函數替代escape 進行編碼。

適用場合不同
encodeURI 被用作對一個完整的URI 進行編碼,而encodeURIComponent 被用作對URI 的一個組件進行編碼。

安全字符不同
escape (69 個) */@+-._0-9a-zA-Z
encodeURI (82 個) !#$&‘()*+,/:;=?@-._~0-9a-zA-Z
encodeURIComponent (71 個) !‘()*-._~0-9a-zA-Z ( 註意+ 號未在其安全字符裏)



其他和Url 編碼相關的問題
對於包含中文的Url 的處理問題,不同瀏覽器有不同的表現。例如對於IE ,如果你勾選了高級設置“ 總是以UTF-8發送Url” ,那麽Url 中的路徑部分 的中文會使用UTF-8 進行Url 編碼之後發送給服務端,而查詢參數中的中文部分使用系統默認字符集進行Url 編碼。為了保證最大互操作性,建議所有放到 Url 中的組件全部顯式指定某個字符集進行Url 編碼,而不依賴於瀏覽器的默認實現。

另外,很多HTTP 監視工具或者瀏覽器地址欄等在顯示Url 的時候會自動將Url 進行一次解碼(使用UTF-8 字符集),這就是為什麽當你在 Firefox 中訪問Google 搜索中文的時候,地址欄顯示的Url 包含中文的緣故。但實際上發送給服務端的原始Url 還是經過編碼的。你可以在地址欄 上使用Javascript 訪問location.href 就可以看出來了。在研究Url 編解碼的時候千萬別被這些假象給迷惑了。

URL中帶加號的處理