如何正确解码传递给servlet的unicode参数

时间:2009-01-22 16:39:18

标签: java unicode servlets

假设我有:

<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"));

我在浏览器中获得 Yahoo!¢。如果我手动将浏览器字符编码切换为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,但它输出 Yahoo!â€,我无法让浏览器呈现正确的字符。

我的问题是:是否存在Content-type和/或new String(request.getParameter("external_title").getBytes(), "UTF-8");和/或其他会导致 Yahoo! TM 出现在SERVLET输出中?

8 个答案:

答案 0 :(得分:44)

你快到了。 EncodeURIComponent正确编码为UTF-8,这是你今天应该在URL中使用的。

问题是提交的查询字符串在进入服务器端脚本的过程中变得很残缺,因为getParameter()使用ISO-8559-1而不是UTF-8。这是源于古代时代之前,网站在UTF-8上确定URI / IRI,但是Servlet规范尚未更新以匹配现实,或者至少为它提供可靠的支持选项,这是相当可悲的。

(Servlet 2.3中有request.setCharacterEncoding,但它不影响查询字符串解析,如果之前已经读过一个参数,可能是其他一些框架元素,它根本不起作用。)< / p>

因此,您需要使用特定于容器的方法来获取正确的UTF-8,通常涉及server.xml中的内容。这完全适合分发应该在任何地方工作的Web应用程序。对于Tomcat,请参阅http://wiki.apache.org/tomcat/FAQ/CharacterEncodingWhat's the difference between "URIEncoding" of Tomcat, Encoding Filter and request.setCharacterEncoding

答案 1 :(得分:18)

我遇到了同样的问题并通过使用URLDecoder()解码Request.getQueryString()并解压缩我的参数后解决了这个问题。

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

答案 2 :(得分:16)

有一种方法可以在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 - 试试这个(省略对decode()的调用,这是不需要的):

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);

如果使用CHARSET_FOR_URI_ENCODING解码的parameter = request.getParameter("name");被破坏,那么这可能会失败(IIUC)因此我用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 =&gt;请参阅评论:parameter=%25CE

答案 3 :(得分:2)

我怀疑请求中发生了数据切割,即请求的声明编码与实际用于数据的编码不匹配。

request.getCharacterEncoding()返回什么?

我真的不知道JavaScript如何处理编码或如何使用特定的编码。

您需要确保在所有阶段都正确使用编码 - 请勿尝试使用new String()getBytes()在已经编码错误的位置“修复”数据。

编辑:原始页面(带有Javascript的页面)也可能有助于以UTF-8编码并在其Content-Type中声明。然后我相信Javascript可能默认使用UTF-8作为其请求 - 但这不是明确的知识,只是猜测。

答案 4 :(得分: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>

答案 5 :(得分:0)

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

encodeURIComponent(escape(lnk.title))

这会给我%25u2122(对于&amp;#8482)或%25AE(对于&amp;#174),它会分别解码为%u2122%AE的servlet。

然后,我应该能够在匹配中使用'\u2122'相对轻松地将%u2122转换为'\u00AE'和%AE转换为(char) (base-10 integer value of %uXXXX or %XX),并使用正则表达式替换循环。

即。 - 匹配/%u([0-9a-f]{4})/i,提取匹配的子表达式,将其转换为base-10,将其转换为char并将其附加到输出,然后对/%([0-9a-f]{2})/i执行相同的操作

答案 6 :(得分:0)

某些Jetty版本中存在一个错误,它会错误地解析更高数量的UTF-8字符。如果您的服务器正确接受阿拉伯字母而不是表情符号,那么这是一个标志您有这个问题的版本,因为阿拉伯语不在ISO-8859-1中,但是在UTF-8字符的较低范围内(“lower”表示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并附加。

答案 7 :(得分:0)

感谢所有我了解的有关tomcat,jetty中使用的默认字符集的编码解码的信息 我使用这种方法来解决我使用谷歌番石榴的问题

        String str = URLDecoder.decode(request.getQueryString(), StandardCharsets.UTF_8.name());
        final Map<String, String> map = Splitter.on('&').trimResults().withKeyValueSeparator("=").split(str);
        System.out.println(map);
        System.out.println(map.get("aung"));
        System.out.println(map.get("aa"));