2009-01-22 67 views
35

假設我有我如何正確解碼的unicode參數:傳遞給一個servlet

<a href="http://www.yahoo.com/" target="_yahoo" 
    title="Yahoo!&#8482;" onclick="return gateway(this);">Yahoo!</a> 
<script type="text/javascript"> 
function gateway(lnk) { 
    window.open(SERVLET + 
     '?external_link=' + encodeURIComponent(lnk.href) + 
     '&external_target=' + encodeURIComponent(lnk.target) + 
     '&external_title=' + encodeURIComponent(lnk.title)); 
    return false; 
} 
</script> 

我已確認external_title編碼爲Yahoo!%E2%84%A2,並傳遞給SERVLET。如果SERVLET我做的:

Writer writer = response.getWriter(); 
writer.write(request.getParameter("external_title")); 

我得到在瀏覽器雅虎「¢!如果我手動將瀏覽器字符編碼切換爲UTF-8,它將更改爲Yahoo! TM(這是我想要的)。

所以我想我發送給瀏覽器的編碼是錯誤的(它是Content-type: text/html; charset=ISO-8859-1)。我改變SERVLET到:

response.setContentType("text/html; charset=utf-8"); 
Writer writer = response.getWriter(); 
writer.write(request.getParameter("external_title")); 

現在瀏覽器的字符編碼是UTF-8,但它輸出雅虎¢,我不能讓瀏覽器渲染正確的字符在所有!

我的問題是:有沒有的Content-type和/或new String(request.getParameter("external_title").getBytes(), "UTF-8");和/或別的某種組合,這將導致雅虎TM出現在SERVLET的輸出?

回答

41

就快成功了。 EncodeURIComponent正確編碼爲UTF-8,這就是你現在應該總是在URL中使用的東西。

問題是提交的查詢字符串在進入服務器端腳本的過程中被破壞,因爲getParameter()使用ISO-8559-1而不是UTF-8。這是源於古代的時代,在網絡以UTF-8爲URI/IRI解決之前,但是Servlet規範尚未更新以符合現實,或者至少爲其提供了可靠的支持選項,這是相當可悲的。

(Servlet 2.3中有request.setCharacterEncoding,但它不影響查詢字符串解析,並且如果之前已經讀取過單個參數,可能還有一些其他框架元素,它根本無法工作。)

因此,您需要使用特定於容器的方法來獲取正確的UTF-8,通常涉及到server.xml中的東西。這完全吸引分發應該在任何地方工作的網絡應用程序。對於Tomcat,請參閱http://wiki.apache.org/tomcat/FAQ/CharacterEncoding以及What's the difference between "URIEncoding" of Tomcat, Encoding Filter and request.setCharacterEncoding

+5

感謝您的解釋。至少我知道我並不瘋狂。我在嘗試request.setCharacterEncoding()的同時尋找解決方案,正如您所說,它似乎沒有做任何事情來幫助解決我的問題。 – 2009-01-22 19:49:32

+0

如果有人使用它,這裏是Jetty的鏈接(默認情況下,Jetty 6+使用UTF-8,除非另有配置):http://docs.codehaus.org/display/JETTY/International+Characters+and+Character+編碼 – 2011-07-16 22:08:15

0

你總是可以使用javascript來進一步操縱文本。

<div id="test">a</div> 
<script> 
var a = document.getElementById('test'); 
alert(a.innerHTML); 
a.innerHTML = decodeURI("Yahoo!%E2%84%A2"); 
alert(a.innerHTML); 
</script> 
+0

是的,decodeURIComponent()返回正確的值,但只有當我從JavaScript中的URL中提取值。如果我試圖decodeURIComponent('<%= request.getParameter(「external_title」)%>');我沒有得到正確的價值。 – 2009-01-22 17:32:45

2

我懷疑數據切割發生在請求,即請求不匹配實際用於該數據的一個所聲明的編碼。

request.getCharacterEncoding()返回什麼?

我真的不知道JavaScript如何處理編碼或如何使其使用特定的編碼。

您需要確保編碼在所有階段都能正確使用 - 不要嘗試在已經錯誤編碼的地方使用new String()getBytes()來「修復」數據。

編輯:它也可能有助於使原始頁面(使用Javascript的)也以UTF-8編碼並在其Content-Type中聲明。然後我相信Javascript可能會默認使用UTF-8來處理它的請求 - 但這不是明確的知識,只是猜測。

+0

request.getCharacterEncoding()返回ISO-8859-1。所以我認爲問題在於encodeURIComponent()將值編碼爲UTF-8,但是它被ISO-8859-1的請求編碼弄亂了。 – 2009-01-22 17:31:12

0

我想我可以得到以下工作:

encodeURIComponent(escape(lnk.title)) 

這給了我%25u2122(爲&#8482)或%25AE(用於&#174),這將分別解碼爲%u2122%AE在servlet的。

然後,我應該能夠將%u2122變成'\u2122'和%AE變成'\u00AE',比較容易在匹配中使用(char) (base-10 integer value of %uXXXX or %XX),並使用正則表達式替換循環。

即 - 比賽/%u([0-9a-f]{4})/i,提取匹配的子表達式,將其轉換爲基數爲10,把它變成一個字符,並追加到輸出,然後做同樣的/%([0-9a-f]{2})/i

+0

這是您可以用來解決Servlet參數字符集問題的一種可能的編碼方案。 (沒有使用惡意JavaScript轉義()函數的可能會更好)。但是任何這樣的參數都不是傳遞參數的標準方式,所以其他任何腳本/表單都不能與它進行通信。 – bobince 2009-01-22 18:39:26

+1

我同意使用escape()不是最好的選擇,但我寧願不在JavaScript中編寫我自己的編碼例程。我已經在IE6,7和8,Firefox2和3,Opera9.6,Safari3.2.1和谷歌瀏覽器中使用escape()測試了我的設計,並且它對這些瀏覽器始終如一地運行。 – 2009-01-22 20:13:36

17

我得到了同樣的問題,解決了它,解碼Request.getQueryString()使用URLDecoder(),並提取我的參數後。

String[] Parameters = URLDecoder.decode(Request.getQueryString(), 'UTF-8') 
         .splitat('&'); 
15

有辦法做到這一點在Java中(與server.xml沒有擺弄)

不起作用:

protected static final String CHARSET_FOR_URL_ENCODING = "UTF-8"; 

String uname = request.getParameter("name"); 
System.out.println(uname); 
// ÏηγÏÏÏÏη 
uname = request.getQueryString(); 
System.out.println(uname); 
// name=%CF%84%CE%B7%CE%B3%CF%81%CF%84%CF%83%CF%82%CE%B7 
uname = URLDecoder.decode(request.getParameter("name"), 
     CHARSET_FOR_URL_ENCODING); 
System.out.println(uname); 
// ÏηγÏÏÏÏη // !!!!!!!!!!!!!!!!!!!!!!!!!!! 
uname = URLDecoder.decode(
     "name=%CF%84%CE%B7%CE%B3%CF%81%CF%84%CF%83%CF%82%CE%B7", 
     CHARSET_FOR_URL_ENCODING); 
System.out.println("query string decoded : " + uname); 
// query string decoded : name=τηγρτσςη 
uname = URLDecoder.decode(new String(request.getParameter("name") 
     .getBytes()), CHARSET_FOR_URL_ENCODING); 
System.out.println(uname); 
// ÏηγÏÏÏÏη // !!!!!!!!!!!!!!!!!!!!!!!!!!! 

作品

final String name = URLDecoder 
     .decode(new String(request.getParameter("name").getBytes(
       "iso-8859-1")), CHARSET_FOR_URL_ENCODING); 
System.out.println(name); 
// τηγρτσςη 

工作,但will break if default encoding != utf-8 - 試試這個(省略瞭解碼的調用( ),它是沒有必要的):

final String name = new String(request.getParameter("name").getBytes("iso-8859-1"), 
     CHARSET_FOR_URL_ENCODING); 

正如我前面所說,如果server.xml與亂作爲:

<Connector connectionTimeout="20000" port="8080" protocol="HTTP/1.1" 
        redirectPort="8443" URIEncoding="UTF-8"/> 

(注意上面的URIEncoding="UTF-8")的代碼將打破(原因getBytes("iso-8859-1")應爲getBytes("UTF-8"))。因此,對於防彈解決方案,您必須獲得URIEncoding屬性的值。這不幸的是似乎是容器特定的 - 甚至更糟的容器版本特定。爲Tomcat 7你需要這樣的:

import javax.management.AttributeNotFoundException; 
import javax.management.InstanceNotFoundException; 
import javax.management.MBeanException; 
import javax.management.MBeanServer; 
import javax.management.MBeanServerFactory; 
import javax.management.MalformedObjectNameException; 
import javax.management.ObjectName; 
import javax.management.ReflectionException; 

import org.apache.catalina.Server; 
import org.apache.catalina.Service; 
import org.apache.catalina.connector.Connector; 

public class Controller extends HttpServlet { 

    // ... 
    static String CHARSET_FOR_URI_ENCODING; // the `URIEncoding` attribute 
    static { 
     MBeanServer mBeanServer = MBeanServerFactory.findMBeanServer(null).get(
      0); 
     ObjectName name = null; 
     try { 
      name = new ObjectName("Catalina", "type", "Server"); 
     } catch (MalformedObjectNameException e1) { 
      e1.printStackTrace(); 
     } 
     Server server = null; 
     try { 
      server = (Server) mBeanServer.getAttribute(name, "managedResource"); 
     } catch (AttributeNotFoundException | InstanceNotFoundException 
       | MBeanException | ReflectionException e) { 
      e.printStackTrace(); 
     } 
     Service[] services = server.findServices(); 
     for (Service service : services) { 
      for (Connector connector : service.findConnectors()) { 
       System.out.println(connector); 
       String uriEncoding = connector.getURIEncoding(); 
       System.out.println("URIEncoding : " + uriEncoding); 
       boolean use = connector.getUseBodyEncodingForURI(); 
       // TODO : if(use && connector.get uri enc...) 
       CHARSET_FOR_URI_ENCODING = uriEncoding; 
       // ProtocolHandler protocolHandler = connector 
       // .getProtocolHandler(); 
       // if (protocolHandler instanceof Http11Protocol 
       // || protocolHandler instanceof Http11AprProtocol 
       // || protocolHandler instanceof Http11NioProtocol) { 
       // int serverPort = connector.getPort(); 
       // System.out.println("HTTP Port: " + connector.getPort()); 
       // } 
      } 
     } 
    } 
} 

而且還是你需要調整這個爲多個連接器(檢查出帶有註釋的部分)。然後,你會使用類似:

new String(parameter.getBytes(CHARSET_FOR_URI_ENCODING), CHARSET_FOR_URL_ENCODING); 

不過這可能會失敗(IIUC)如果parameter = request.getParameter("name");與CHARSET_FOR_URI_ENCODING解碼被損壞,所以我用的getBytes得到字節()是不是原來的那些(這就是爲什麼「ISO-8859- 1「默認使用 - it will preserve the bytes)。

URLDecoder.decode(request.getQueryString().split("=")[1], 
     CHARSET_FOR_URL_ENCODING); 

我仍然在尋找在那裏提到request.getParameter("name")在文檔中的位置並調用URLDecoder.decode(),而不是返回%CF%84%CE%B7%CE%B3%CF%81%CF%84%CF%83%CF%82%CE%B7:您可以通過手動解析中的行查詢字符串擺脫這一切字符串?源中的鏈接將不勝感激。
另外,我怎樣才能通過參數的值字符串,比如%CE =>請參閱評論:parameter=%25CE

0

Jetty的某些版本中存在一個錯誤,它會錯誤地解析更高數字的UTF-8字符。如果你的服務器正確地接受了阿拉伯文字母而不是表情符號,那麼你就有這個問題的版本,因爲阿拉伯語不在ISO-8859-1中,但是在UTF-8字符的較低範圍內(「較低」表示java將用一個字符表示)。

我已從版本7.2.0.v20101020更新到版本7.5.4.v20111024,並解決了此問題;我現在可以使用getParameter(String)方法,而不必自己解析它。

如果你真的好奇,你可以挖掘你的org.eclipse.jetty.util.Utf8StringBuilder.append(byte)版本,並看看它是否正確地添加多個字符串的utf-8代碼是足夠高或如果在7.2.0中,它只是將一個int轉換爲char並追加。