Java URL查询字符串参数的编码

825

假设我有一个URL地址

http://example.com/query?q=

我有一个用户输入的查询语句,例如:

随机单词 £500 银行 $

我希望结果是一个正确编码的URL:

http://example.com/query?q=random%20word%20%A3500%20bank%20%24

哪种方法最好实现这个目标?我尝试使用URLEncoder和创建URI/URL对象,但它们都不完全正确。


30
“none of them come out quite right” 的意思是“它们都不太对劲”。 - Mark Elliot
2
我使用了URI.create并在querystring中用+替换了空格。在客户端站点上,当我选择查询字符串时,它将+转换回空格。这对我来说很有效。 - ND27
1
你为什么期望 $ 被百分号编码? - jschnasse
11个回答

1338
URLEncoder 是最好的选择。您只需要记住对单独的查询字符串参数名称和/或值进行编码,而不是整个URL,确保不编码查询字符串参数分隔符字符 & 或参数名称-值分隔符字符 =
String q = "random word £500 bank $";
String url = "https://example.com?q=" + URLEncoder.encode(q, StandardCharsets.UTF_8);
当您仍未使用Java 10或更高版本时,请使用StandardCharsets.UTF_8.toString()作为charset参数;当您仍未使用Java 7或更高版本时,请使用"UTF-8"
请注意,在查询参数中,空格由+表示,而不是%20,尽管%20也是合法的。通常情况下,%20用于表示URI本身中的空格(即查询字符串分隔符字符?之前的部分),而不是查询字符串(?之后的部分)。
还要注意,有三个encode()方法。其中一个没有第二个参数Charset,另一个带有String作为第二个参数并抛出已检查异常。没有Charset参数的方法已被弃用。永远不要使用它,而是始终指定Charset参数。即使在javadoc中,也明确推荐使用UTF-8编码,这是由RFC3986W3C所规定的。

所有其他字符都不安全,并且首先使用某种编码方案将它们转换为一个或多个字节。然后,每个字节用三个字符的字符串“%xy”表示,其中xy是字节的二位十六进制表示。建议使用的编码方案是UTF-8。但是出于兼容性原因,如果未指定编码,则使用平台的默认编码。

另请参阅:Web开发人员必须了解的URL编码知识

1
URL 中可能有两种类型的参数。查询字符串(后跟 ?)和路径参数(通常是 URL 的一部分)。那么,路径参数怎么办呢?URLEncoder 甚至对于路径参数也会将空格转换为 +。实际上,它只处理查询字符串以外的内容。此外,这种行为与 Node.js 服务器不同步。因此,对我来说,这个类是浪费的,除非是非常特定/特殊的情况下才能使用。 - sharadendu sinha
8
如文档所述,URLEncoder 用于符合 application/x-www-form-urlencoded 规则的 URL 编码查询参数。路径参数不适用于此类别。你需要使用 URI 编码器。 - BalusC
2
正如我之前预测的那样...用户会感到困惑,因为很明显问题不仅仅在于需要编码参数值。只需要编码参数值的情况非常罕见。这就是为什么我提供了我的“困惑”维基答案,以帮助像@sharadendusinha这样的人。 - Adam Gent
1
@WijaySharma:因为URL特定字符也会被编码。只有当您想将整个URL作为另一个URL的查询参数传递时,才应该这样做。 - BalusC
2
“+, 不是%20” 这正是我想要听到的。非常感谢。 - wetjosh
显示剩余6条评论

202

我不会使用URLEncoder,因为它的名称不正确(URLEncoder与URL无关),效率低下(它使用StringBuffer而不是Builder,并执行一些缓慢的操作)。此外,也很容易搞砸。

相反,我会使用URIBuilderSpring的org.springframework.web.util.UriUtils.encodeQuery或Commons Apache HttpClient。原因是您必须以不同的方式转义查询参数名称(即BalusC的答案q)和参数值。

上述方法唯一的缺点(我痛苦地发现了)是URL不是URI的真正子集

示例代码:

import org.apache.http.client.utils.URIBuilder;

URIBuilder ub = new URIBuilder("http://example.com/query");
ub.addParameter("q", "random word £500 bank \$");
String url = ub.toString();

// Result: http://example.com/query?q=random+word+%C2%A3500+bank+%24

2
为什么这与URL无关? - Luis Sep
22
@Luis说:URLEncoder的Java文档中指出,它旨在按照HTML规范描述的 application/x-www-form-urlencoded 格式编码查询字符串参数:http://www.w3.org/TR/html4/interact/forms.html#didx-applicationx-www-form-urlencoded。一些用户确实会混淆/滥用它来编码整个URI,就像当前答复者显然所做的那样。 - BalusC
9
简而言之,URLEncoder是用于表单提交时的编码,而不是用于转义。它并不完全相同于你在网页中创建URL所使用的转义方式,但它们足够相似以至于人们经常滥用它。唯一应该使用URLEncoder的情况是当你编写HTTP客户端(即便如此,也有更好的编码选项可供选择)。 - Adam Gent
1
@BalusC:“确实有一些用户会混淆/滥用它来对整个URI进行编码,就像当前的回答者显然做的那样。”你的假设是错误的。我从未说过我在使用它时出了问题。我只是见过其他人这样做,并且需要修复他们的错误。我搞砸的部分是Java URL类将接受未转义的括号,但URI类不会。构造URL的方法有很多种,并非每个人都像您一样聪明。我认为大多数在SO上寻找URLEncoding的用户可能确实会“混淆/滥用”URI转义。 - Adam Gent
1
是的,他正在连接字符串以创建URL。URLEncoder用于编码到MIME类型,而不是用于制作URL。我对声誉没有兴趣(因此我将其标记为维基帖子,并且只想正确引导人们)。 - Adam Gent
显示剩余3条评论

115

您需要先创建一个类似于以下的URI:

String urlStr = "http://www.example.com/CEREC® Materials & Accessories/IPS Empress® CAD.pdf"
URL url = new URL(urlStr);
URI uri = new URI(url.getProtocol(), url.getUserInfo(), url.getHost(), url.getPort(), url.getPath(), url.getQuery(), url.getRef());

然后将该URI转换为ASCII字符串:

urlStr = uri.toASCIIString();

现在您的URL字符串已完全编码。首先,我们进行了简单的URL编码,然后将其转换为ASCII字符串,以确保字符串中没有美国ASCII之外的字符。这正是浏览器所做的。


8
谢谢!你的解决方案虽然奇怪,但有效,而内置的URL.toURI()却不行。 - user11153
3
不幸的是,这似乎无法与“file:///”一起使用(例如:“file:///some/directory/a file containing spaces.html”);它在“new URL()”中出现MalformedURLException错误;有什么想法如何解决这个问题? - ZioByte
1
@tibi,你可以直接使用uri.toString()方法将其转换为字符串,而不是Ascii字符串。 - M Abdul Sami
1
我正在使用的API不接受+替换空格,但接受%20,因此这个解决方案比BalusC更好,谢谢! - Julian Honma
2
这是编码URL路径组件的正确方式。但这不是编码查询参数名称或值的正确方式,这也是问题所在。 - user207421
显示剩余7条评论

37

2
这些与URLEncoder相同,都受到愚蠢的转义规则的影响。 - 2rs2ts
4
不确定他们是否有这个问题。例如,他们区分“+”或“%20”以转义“ ”(表单参数或路径参数),而 URLEncoder 不会这样做。 - Emmanuel Touzery
1
这对我有用,我只是将调用URLEncoder()替换为调用UrlEscapers.urlFragmentEscaper(),然后它就起作用了,不清楚是否应该使用UrlEscapers.urlPathSegmentEscaper()。 - Paul Taylor
2
实际上,它对我没有起作用,因为与URLEncoder不同,它不会对'+'进行编码,而是保持原样。服务器将'+'解码为空格,而如果我使用URLEncoder,则'+'将转换为%2B,并正确地解码回'+'. - Paul Taylor
2
链接更新:UrlEscapers - mgaert

9

这段代码

URL url = new URL("http://example.com/query?q=random word £500 bank $");
URI uri = new URI(url.getProtocol(), url.getUserInfo(), IDN.toASCII(url.getHost()), url.getPort(), url.getPath(), url.getQuery(), url.getRef());
String correctEncodedURL = uri.toASCIIString();
System.out.println(correctEncodedURL);

打印

http://example.com/query?q=random%20word%20%C2%A3500%20bank%20$

这里正在发生什么?

1. 将URL拆分为结构部分。使用java.net.URL进行操作。

2. 正确地对每个结构部分进行编码!

3. 使用IDN.toASCII(putDomainNameHere)对主机名进行Punycode编码!

4. 使用java.net.URI.toASCIIString()进行百分比编码,针对NFC编码的Unicode - (更好的选择是NFKC!)。有关更多信息,请参见:如何正确编码此URL

在某些情况下,建议检查是否已对URL进行了编码。还需将以“+”编码的空格替换为以“%20”编码的空格。

以下是一些也能正常工作的示例

{
      "in" : "http://نامه‌ای.com/",
     "out" : "http://xn--mgba3gch31f.com/"
},{
     "in" : "http://www.example.com/‥/foo",
     "out" : "http://www.example.com/%E2%80%A5/foo"
},{
     "in" : "http://search.barnesandnoble.com/booksearch/first book.pdf",
     "out" : "http://search.barnesandnoble.com/booksearch/first%20book.pdf"
}, {
     "in" : "http://example.com/query?q=random word £500 bank $",
     "out" : "http://example.com/query?q=random%20word%20%C2%A3500%20bank%20$"
}

这个解决方案通过了由Web Platform Tests提供的大约100个测试用例。


7

1
"URLEncodedUtils" 的链接已经失效(404)。 - Peter Mortensen

7

1
对于Spring用户,确认此解决方案有效!!! - cppxaxa

5
以下是您可以在代码中使用的方法,将URL字符串和参数映射转换为包含查询参数的有效编码URL字符串。
String addQueryStringToUrlString(String url, final Map<Object, Object> parameters) throws UnsupportedEncodingException {
    if (parameters == null) {
        return url;
    }

    for (Map.Entry<Object, Object> parameter : parameters.entrySet()) {

        final String encodedKey = URLEncoder.encode(parameter.getKey().toString(), "UTF-8");
        final String encodedValue = URLEncoder.encode(parameter.getValue().toString(), "UTF-8");

        if (!url.contains("?")) {
            url += "?" + encodedKey + "=" + encodedValue;
        } else {
            url += "&" + encodedKey + "=" + encodedValue;
        }
    }

    return url;
}

3

在Android中,我会使用以下代码:

Uri myUI = Uri.parse("http://example.com/query").buildUpon().appendQueryParameter("q", "random word A3500 bank 24").build();

其中Uri是一个android.net.Uri


13
请指明所使用的库,因为这不是使用标准的Java API。 - rmuller
这是在Android项目中使用的最简单的解决方案。 - alierdogan7

1
在我的情况下,我只需要传递整个URL并仅对每个参数的值进行编码。 我没有找到通用的代码来实现这一点,所以(!!)我创建了这个小方法来完成这项工作:
public static String encodeUrl(String url) throws Exception {
    if (url == null || !url.contains("?")) {
        return url;
    }

    List<String> list = new ArrayList<>();
    String rootUrl = url.split("\\?")[0] + "?";
    String paramsUrl = url.replace(rootUrl, "");
    List<String> paramsUrlList = Arrays.asList(paramsUrl.split("&"));
    for (String param : paramsUrlList) {
        if (param.contains("=")) {
            String key = param.split("=")[0];
            String value = param.replace(key + "=", "");
            list.add(key + "=" +  URLEncoder.encode(value, "UTF-8"));
        }
        else {
            list.add(param);
        }
    }

    return rootUrl + StringUtils.join(list, "&");
}

public static String decodeUrl(String url) throws Exception {
    return URLDecoder.decode(url, "UTF-8");
}

它使用Apache Commons' org.apache.commons.lang3.StringUtils

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