HttpClient和非ASCII URL字符(á,é,í,ó,ú)

4

我是一位长期的读者,第一次发帖。

我正在制作一个机器人,用于我管理的西班牙语维基。我想从头开始制作它,因为我制作它的目的之一是为了练习Java。然而,当我尝试使用HttpClient进行GET请求到包含非ASCII字符(如á,é,í,ó或ú)的URI时,遇到了一些问题。

String url = "http://es.metroid.wikia.com/api.php?action=query&list=categorymembers&cmtitle=Categoría:Mejoras de las Botas"
method = new GetMethod(url);
client.executeMethod(method);

当我执行上述操作时,GetMethod会抱怨URI:
Exception in thread "main" java.lang.IllegalArgumentException: Invalid uri 'http://es.pruebaloca.wikia.com/api.php?action=query&list=categorymembers&cmtitle=Categoría:Mejoras%20de%20las%20Botas&cmlimit=500&format=xml': Invalid query
    at org.apache.commons.httpclient.HttpMethodBase.<init>(HttpMethodBase.java:222)
    at org.apache.commons.httpclient.methods.GetMethod.<init>(GetMethod.java:89)
    at net.metroidover.categorybot.http.HttpRequest.request(HttpRequest.java:69)
    at net.metroidover.categorybot.http.HttpRequest.request(HttpRequest.java:120)
    at net.metroidover.categorybot.http.Action.getCategoryMembers(Action.java:38)
    at net.metroidover.categorybot.bot.BotComponent.<init>(BotComponent.java:58)
    at net.metroidover.categorybot.bot.BotComponent.main(BotComponent.java:80)

请注意堆栈跟踪中显示的URI,其中空格被编码为%20,而í保持不变。在浏览器上,完全相同的URI可以正常工作,但我无法使GetMethod接受它。
我还尝试了以下操作:
URI uri = new URI(url, false);
method = new GetMethod(uri.getEscapedURI());
client.executeMethod(method);

这样,URI 转义了 i,但是对空格进行了双重转义(%2520)...
http://es.metroid.wikia.com/api.php?action=query&list=categorymembers&cmtitle=Categor%C3%ADa:Mejoras%2520de%2520las%2520Botas&cmlimit=500&format=xml

现在,如果我在查询中不使用任何空格,就不会出现双重转义,并且我可以得到所需的输出。因此,如果不存在非ASCII字符的可能性,我就不需要使用URI类,并且不会出现双重转义。为了避免第一次空格转义,我尝试了这个:
URI uri = new URI(url, true);
method = new GetMethod(uri.getEscapedURI());
client.executeMethod(method);

但是URI类不喜欢它:
org.apache.commons.httpclient.URIException: Invalid query
    at org.apache.commons.httpclient.URI.parseUriReference(URI.java:2049)
    at org.apache.commons.httpclient.URI.<init>(URI.java:167)
    at net.metroidover.categorybot.http.HttpRequest.request(HttpRequest.java:66)
    at net.metroidover.categorybot.http.HttpRequest.request(HttpRequest.java:121)
    at net.metroidover.categorybot.http.Action.getCategoryMembers(Action.java:38)
    at net.metroidover.categorybot.bot.BotComponent.<init>(BotComponent.java:58)
    at net.metroidover.categorybot.bot.BotComponent.main(BotComponent.java:80)
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index: 1, Size: 0
    at java.util.ArrayList.RangeCheck(ArrayList.java:547)
    at java.util.ArrayList.get(ArrayList.java:322)
    at net.metroidover.categorybot.http.Action.getCategoryMembers(Action.java:39)
    at net.metroidover.categorybot.bot.BotComponent.<init>(BotComponent.java:58)
    at net.metroidover.categorybot.bot.BotComponent.main(BotComponent.java:80)

任何关于如何避免这种双重转义的输入都将不胜感激。我已经四处搜索,但没有一点运气。
谢谢!
编辑:对我最有效的解决方案是parsifal的解决方案,但是作为一个补充,我想说用method.setPath(url)设置路径会使HttpMethod拒绝我需要保存的cookie。
Aug 26, 2011 4:07:08 PM org.apache.commons.httpclient.HttpMethodBase processCookieHeaders
WARNING: Cookie rejected: "wikicities_session=900beded4191ff880e09944c7c0aaf5a". Illegal path attribute "/". Path of origin: "http://es.metroid.wikia.com/api.php"

然而,如果我将URI发送给构造函数并忘记setPath(url),则Cookie会被成功保存。
String url = "http://es.metroid.wikia.com/api.php";
NameValuePair[] query = { new NameValuePair("action", "query"), new NameValuePair("list", "categorymembers"),
            new NameValuePair("cmtitle", "Categoría:Mejoras de las Botas"), new NameValuePair("cmlimit", "500"),
            new NameValuePair("format", "xml") };
HttpMethod method = null;

...

method = new GetMethod(url);  // Or PostMethod(url)
method.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY); // It had been like this the whole time
method.setQueryString(query);
client.executeMethod(method);
3个回答

5
我建议使用 UrlEncoder 对你的查询字符串值进行编码(而不是整个查询字符串)。
UrlEncoder.encode("Categoría:Mejoras de las Botas", "UTF-8");

这个方法运行得相当不错,但你必须单独对所有查询参数进行编码。我认为parsifal的答案更有用,因为使用method.setQueryString(pairs);时,所有的NameValuePair都会一次性被编码,其中pairs是一个NameValuePair[]。 - ianmartorell

2

看一下 HttpMethodBase 的文档,看起来所有 String 参数都必须预先编码。最简单的解决方案是分阶段构造您的 URL,使用 setPath() 和带有名称-值参数数组的 setQueryString() 变体。


太好了!这个完美地运行了。实际上,我已经将参数作为 ArrayList<NameValuePair> 发送了,所以我没有改变太多的代码。谢谢 :) - ianmartorell

-1

为什么不尝试将参数添加为 NameValuePair,问题在于当您转义URL时,包括诸如 http://.. 在内的 URL 中的所有内容都被转义了,这就是系统报错的原因。

您还可以使用 URLEncoder.encode() 转义参数,只需将 get 参数传递给它,并将返回值附加到 URL 上即可。

String url = "http://es.metroid.wikia.com/api.php?"+URLEncoder.encode("action=query&list=categorymembers&cmtitle=Categoría:Mejoras de las Botas");


我认为每个参数都必须单独编码,否则&和=将被编码。 - JB Nizet
就像 @JB Nizet所说的那样,你必须单独对其进行编码,否则会得到http://es.metroid.wikia.com/api.php?action%3Dquery%26list%3Dcategorymembers%26c‌​mtitle%3DCategor%C3%ADa%3AMejoras+de+las+Botas - ianmartorell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接