2009-01-22 96 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并追加。