1. 程式人生 > >揉碎HTTP編碼過程,從此不亂碼

揉碎HTTP編碼過程,從此不亂碼

老生常談之HTTP亂碼問題

最後的結論?

對於get請求 在Servlet中呼叫request.setCharacterEncoding()設定編碼是沒有意義的不管你使用任何編碼方式對於你的資料解碼沒有任何影響

問題的引出,在tomcat9之後,不管你在setCharacterEncoding()設定什麼編碼都不會亂碼,哪怕是最基礎的ACSII,那有人就說了,那不挺好的,不亂嗎就行了唄;

沒辦法,我喜歡問自己為什麼,憑什麼不亂嗎,那設定這個編碼有什麼意義?

網上找了一下,只有問為什麼亂碼,沒人問為啥不亂碼,總有人會說設定編碼要放在獲取引數前,諸如此類,說的一套一套,人云亦云!

帶著這個問題我寫了一個簡單的socket程式 目的就是想伺服器傳送一個HTTP請求手動控制編碼環節,

第一次測試,直接把中文寫在url中,最後用utf-8來發送給伺服器

伺服器返回400請求解析失敗

Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986

這啥玩意?,原來RFC 3986中規定URL中只允許包含24個英文字母以及基本的字元,中文編碼後其對應的二進位制超過了規定返回導致報錯,這一點很重要

這導致了我們在URL中不能傳遞中文引數,那不行,得解決這個問題,於是就出現了URLEncoding

URLEncoding

我們想要在url中包含中文,就必須先把中文轉換為基本的字元,其原理是字元轉為16進位制的字元然後在每個位元組前加一個%,就像下面這樣

結果:%e4%bd%a0

把這個字元拿到網頁中URL解碼

沒問題

模擬瀏覽器傳送請求

注意這裡資料依然是字串格式的,要想通過socket傳送則必須在進行編碼,例如我們要請求的地址為/untitled3/TestServlet?username=你

我們先要使用URLEncoding將轉為符合RFC 3986要求的字串,替換到原本的位置去

處理過的地址為/untitled3/TestServlet?username=%e4%bd%a0

然後在把URL放到我們的HTTP請求報文中

伺服器端doGet方法

解析成功

ok伺服器已經成功解析了中文但是注意我在伺服器端指定的編碼為ASCII,這也是我要解決的主要問題,

總結時間:

客戶端傳送請求時資料一共經過了兩次編碼,

  • 第一次是把中文轉為符合要求的字元(URLEncoding),注意還是字元
  • 第二次就是我們把整個HTTP資料進行統一編碼為二進位制

那麼我們伺服器端執行的這個setCharacterEncoding是用在那一次呢?

沒錯就是第一次,這就是為什麼我們無論設定那種編碼都不會導致亂碼的原因了,

決定是否亂碼的核心在第二次,在上面的例子中伺服器先是用ASCII來解析http整個資料包,其中的的URL為/untitled3/TestServlet?username=%e4%bd%a0

對於這個URL而言任何編碼表都能解析,不會亂碼,接下里就需要將裡面的引數部分(問號後面的)拿出來進行反向URLEncoding,此時的反向解析就是最重要的要從字串中提取16進位制資料,在通過編碼表進行解碼 ,

而tomcat9中預設的解碼方式為UTF-8,到這裡你應該明白了,為什麼不亂嗎?

說人話:

簡單的說:對於get請求而言,呼叫setCharacterEncoding是沒有任何意義的,如果我們要控制URLEncoding的解碼方式,必須通過server.xml來修改

在囉嗦一下,對於post請求,我們的資料是包含在請求體中的所以,上面的配置對於post請求沒有效果,那如果請求體中包含了中文怎麼辦,很簡單隻要與客戶端保持相同的解碼方式即可,使用request.setCharacterEncoding方法來設定,

還有問題,為什麼post就可以呢? 因為RFC 3986只是說URL中的字元位元組必須在某個範圍內,沒有限制請求體中的資料範圍,所以對於請求體,你愛放中就放中文,

你也可以這麼理解request.setCharacterEncoding只是用來設定請求體的解碼方式,對於url中的引數解碼方式就必須同 server.xml來配置

另外對於post伺服器其實可以不設定編碼只要客戶端post請求頭的ContentType中聲明瞭編碼即可,這也側面印證了post才是用來給伺服器傳遞資料的更優方法,

那為什麼大家喜歡用get呢?不知道,或許是因為簡單?,累了就到這裡吧

補充下1樓的解決亂碼方案也是可以的
其原理是將使用錯誤碼錶解碼的結果還原為二進位制,再用正確碼錶重新解碼
當然你不管用哪種方式都必須與對方採用相同的編碼方式
只要理解了其中原理,那亂碼問題也就迎刃而解了