如何正确编码此URL

6

我正在尝试使用JSoup获取此URL

http://betatruebaonline.com/img/parte/330/CIGUEÑAL.JPG

即使使用编码,我仍然遇到了异常。 我不明白为什么编码是错误的。它返回

http://betatruebaonline.com/img/parte/330/CIGUEN%C3%91AL.JPG

相反,正确的是

http://betatruebaonline.com/img/parte/330/CIGUEN%CC%83AL.JPG

我该如何修复这个问题? 谢谢。

private static void GetUrl()
{
    try
    {
        String url = "http://betatruebaonline.com/img/parte/330/";
        String encoded = URLEncoder.encode("CIGUEÑAL.JPG","UTF-8");
        Response img = Jsoup
                            .connect(url + encoded)
                            .ignoreContentType(true)
                            .execute();

        System.out.println(url);
        System.out.println("PASSED");
    }
    catch(Exception e)
    {
        System.out.println("Error getting url");
        System.out.println(e.getMessage());
    }
}

这只是一个文件未找到的异常,当执行时它会作为HTTP错误代码404。请确保请求的URL资源在此时存在。 - tommybee
首先,%C3%91 是一个完整的 Ñ 字符,不需要在前面加上 N。因此 N%C3%91 确实是一个 序列,而不是单个字符。 - Luis Colorado
4个回答

5

编码没有问题,这里的问题在于字符“Ñ”的组合unicode和预组合unicode可以以两种方式显示,它们看起来相同但实际上不同。

precomposed unicode: Ñ           -> %C3%91
composite unicode: N and ~       -> N%CC%83

我强调两种方式都是正确的,这取决于您想要哪种类型的Unicode编码:
String normalize = Normalizer.normalize("Ñ", Normalizer.Form.NFD);
System.out.println(URLEncoder.encode("Ñ", "UTF-8")); //%C3%91
System.out.println(URLEncoder.encode(normalize, "UTF-8")); //N%CC%83

4

这里发生了什么?

如@yelliver所述,Web服务器似乎在其路径名中使用NFD编码的Unicode。因此,解决方案是使用相同的编码。

Web服务器是否正确?

1. 对于那些好奇的人(像我一样),这篇关于多语言Web地址的文章为该主题带来了一些光明。在IRI路径部分(实际由Web服务器处理的部分)中,它指出:

虽然域名注册机构都可以同意接受特定形式和编码(基于ASCII的punycode)的域名,但多脚本路径名称标识位于许多平台上的资源,其文件系统使用许多不同的编码,而且将继续使用许多不同的编码。这使得路径比域名更难处理。

2. 关于如何编码路径的更多信息可以在IRI国际化资源标识符(IRIs)的IETF建议标准的第5.3.2.2节中找到rfc3987。它说:

IRI的等价性必须依赖于假设IRI已经适当地预字符规范化,而不是在比较两个IRI时应用字符规范化。例外情况包括从非数字形式转换和从非基于UCS的字符编码转换。在这些情况下,必须使用NFC或使用NFC的归一化转码器进行互操作性。为避免误报和转码问题,应使用NFC创建IRI。使用NFKC可能会避免更多问题;例如,通过选择半角拉丁字母而不是全角字母以及选择全角而不是半角片假名。

3. Unicode Consortium 表示:

NFKC是标识符的首选形式,特别是存在安全问题时(请参见UTR#36)。 NFD和NFKD对于内部处理最有用。

结论

问题中提到的Web服务器不符合IRI标准或Unicode联盟的建议,并使用NFD编码而不是NFC或NFKC。正确编码URL字符串的一种方法是按如下方式进行。

URI uri = new URI(url.getProtocol(), url.getUserInfo(), IDN.toASCII(url.getHost()), url.getPort(), url.getPath(), url.getQuery(), url.getRef());

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

String correctEncodedURL=uri.toASCIIString(); 

toASCIIString() 调用 encode() 方法,该方法使用 NFC 编码的 Unicode。 IDN.toASCII() 将主机名转换为 Punycode


1
感谢您撰写了这篇解释。它对于其他遇到相同或类似问题的人来说可能会很有帮助。 - ppk
有关此答案中提供的代码示例的进一步解释,请查看此处 - jschnasse

1
实际上,在URL编码之前,您需要将URL转换为分解形式。
这里有一个使用Guava和java.text.Normalizer的解决方案:
import com.google.common.escape.Escaper;
import com.google.common.net.UrlEscapers;
import org.jsoup.Connection;
import org.jsoup.Jsoup;

import java.text.Normalizer;

public class JsoupImageDownload {

    public static void main(String[] args) {

        String url = "http://betatruebaonline.com/img/parte/330/CIGUEÑAL.JPG";
        String encodedurl = null;
        try {
            encodedurl = Normalizer.normalize(url, Normalizer.Form.NFD);
            Escaper escaper = UrlEscapers.urlFragmentEscaper();
            encodedurl = escaper.escape(encodedurl);
            Connection.Response img = Jsoup
                    .connect(encodedurl)
                    .ignoreContentType(true)
                    .execute();

            System.out.println(url);
            System.out.println("PASSED");
        } catch (Exception e) {
            System.out.println("Error getting url: " + encodedurl);
            System.out.println(e.getMessage());
        }
    }
}

这些是Maven依赖项:
<!-- https://mvnrepository.com/artifact/org.jsoup/jsoup -->
<dependency>
    <groupId>org.jsoup</groupId>
    <artifactId>jsoup</artifactId>
    <version>1.11.2</version>
</dependency>

<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>24.1-jre</version>
</dependency>

0
非常简单的解决方案: 编码系统提供的和您需要的不同,因此以下解决方案将对您有所帮助。
private static void GetUrl(String url)
{
    try
    {

        String encodedurl = url.replace("Ñ","N%CC%83");
        Response img = Jsoup
                            .connect(encodedurl)
                            .ignoreContentType(true)
                            .execute();

        System.out.println(url);
        System.out.println("PASSED");
    }
    catch(Exception e)
    {
        System.out.println("Error getting url");
        System.out.println(e.getMessage());
    }
}

问题在于URL列表中可能还有其他字符,导致代码在运行时失败而无法正常工作。这就是为什么不能使用这种方法的原因。 - ppk
1
那个解决方案是不正确的。它会导致“%”字符被编码为“%25”序列,而且你会遇到更多麻烦。 - Luis Colorado
1
以下答案工作正常,但还有一个问题Normalizer.Form.NFD,如何知道应该使用哪种形式,NFD还是NFC,还有2种类型可用。如果我们使用NFD,那么意味着我们认为字符是复合字符,但并不总是正确的。 - Dupinder Singh

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