创建URI时为什么会声明主机名无效

15

使用JDK 1.8运行此代码:

try {
    System.out.println( new URI(null, null, "5-12-145-35_s-81", 443, null, null, null));
} catch (URISyntaxException e) {
    e.printStackTrace();
}

这个错误的原因是: java.net.URISyntaxException: Illegal character in hostname at index 13: //5-12-145-35_s-81:443

考虑到所有主机名字符都符合URI字符类型,那么这个错误是从哪里来的呢?


如果我使用这些URL://5-12-145-35_s-81:443或者/5-12-145-35_s-81:443,错误就会消失。


根据评论,根据RFC-2396 ,主机名不能包含任何下划线字符。

仍然存在的问题是,为什么一个以斜杠或双斜杠开头的主机名允许包含下划线?


1
@ernest_k 方案未给出,为null。 - user10871691
如果您仍然想在URL中使用“_”,那么@fg78nc的解决方法将适用于您。不要使用“/”,因为主机名将无效,也不会创建字段。 - cvekaso
3
请参见RFC-2396第3.2.2节。URI中的主机名只能是一个或多个字母数字和减号“-”组成的分组,它们之间由点号分隔。 - Mark Rotteveel
@MarkRotteveel,java.net.URI没有更新到最新的规范。 - fg78nc
尽管RFC-3986放宽了限制,但仍提到“旨在在DNS中查找的注册名称使用[RFC1034]第3.5节和[RFC1123]第2.1节中定义的语法。”,这基本上是RFC-2396第3.2.2节的语法。@fg78nc - Mark Rotteveel
4个回答

7

主机名必须符合以下语法:

hostname      = domainlabel [ "." ] | 1*( domainlabel "." ) toplabel [ "." ]
domainlabel   = alphanum | alphanum *( alphanum | "-" ) alphanum
toplabel      = alpha | alpha *( alphanum | "-" ) alphanum

正如您所看到的,只允许使用.-,不允许使用_


然后您说//5-12-145-35_s-81:443是允许的,确实如此,但不适用于主机名

为了了解情况如何发展:

URI uriBadHost = URI.create("//5-12-145-35_s-81:443");
System.out.println("uri = " + uriBadHost);
System.out.println("  authority = " + uriBadHost.getAuthority());
System.out.println("  host = " + uriBadHost.getHost());
System.out.println("  port = " + uriBadHost.getPort());

URI uriGoodHost = URI.create("//example.com:443");
System.out.println("uri = " + uriGoodHost);
System.out.println("  authority = " + uriGoodHost.getAuthority());
System.out.println("  host = " + uriGoodHost.getHost());
System.out.println("  port = " + uriGoodHost.getPort());

输出

uri = //5-12-145-35_s-81:443
  authority = 5-12-145-35_s-81:443
  host = null
  port = -1

uri = //example.com:443
  authority = example.com:443
  host = example.com
  port = 443

正如您所看到的,当authority有一个有效的主机名时,hostport被解析,但当不合法时,authority被视为自由格式文本,不再进一步解析。

更新

来自评论:

System.out.println( new URI(null, null, "/5-12-145-35_s-81", 443, null, null, null))输出: ///5-12-145-35_s-81:443。我正在将其作为主机名。

您调用的URI构造函数是一个方便方法,它只需构建一个完整的URI字符串,然后对其进行解析。

传递"5-12-145-35_s-81", 443变成//5-12-145-35_s-81:443
传递"/5-12-145-35_s-81", 443变成///5-12-145-35_s-81:443

在第一个示例中,它是一个hostport,无法解析。
在第二个示例中,authority部分为空,/5-12-145-35_s-81:443是一个path

URI uri1 = new URI(null, null, "/5-12-145-35_s-81", 443, null, null, null);
System.out.println("uri = " + uri1);
System.out.println("  authority = " + uri1.getAuthority());
System.out.println("  host = " + uri1.getHost());
System.out.println("  port = " + uri1.getPort());
System.out.println("  path = " + uri1.getPath());

输出

uri = ///5-12-145-35_s-81:443
  authority = null
  host = null
  port = -1
  path = /5-12-145-35_s-81:443

现在我明白了,但是为什么,比如说/a_b,是被允许的呢?唯一的区别在于这个是绝对路径,不是相对路径。 - user10871691
System.out.println(new URI(null, null, "/5-12-145-35_s-81", 443, null, null, null))输出:///5-12-145-35_s-81:443。我将其作为主机名。 - user10871691
这种行为(当主机名是绝对时)至少可以说是奇怪的。URI的构造函数提供了一个主机名和端口,但结果的URI既没有主机名也没有端口,只有路径。 - user10871691

5

URI中不支持下划线。

虽然主机名不能包含其他字符,例如下划线(_)等字符,但是其他DNS名称可以包含下划线。 [5] [6] RFC 2181第11节取消了此限制。DomainKeys和服务记录等系统使用下划线作为一种确保其特殊字符不会与主机名混淆的方法。例如,_http._sctp.www.example.com指定了示例.com域中SCTP-capable webserver主机(www)的服务指针。尽管存在标准规定,但Chrome,Firefox,Internet Explorer,Edge和Safari允许在主机名中使用下划线,但是如果主机名的任何部分包含下划线字符,则IE中的cookie无法正确工作。

维基百科

从Javadocs中得知:

public URI(String str) throws URISyntaxException Throws: URISyntaxException - 如果给定字符串违反RFC 2396,并通过以上偏差进行增强

Javadocs

(可行但有些繁琐的)解决方案:

    URI url = URI.create("https://5-12-145-35_s-8:8080");

    System.out.println(url.getHost()) // null

    if (url.getHost() == null) {
        final Field hostField = URI.class.getDeclaredField("host");
        hostField.setAccessible(true);
        hostField.set(url, "5-12-145-35_s-81");
    }
    System.out.println(url.getHost()); // 5-12-145-35_s-81

这被报告为 - JDK错误


1
哇,那是一个不太正规的解决方案。你可以指出这可能会在未来出现问题,因为它假设了一个内部类的内部细节,并使用反射直接访问它。因此,实现可能会随着任何Java版本的更改而改变,在这种情况下,这个解决方案可能会失效。但是,还是要给你点赞提供了一个解决方案。 - Zabuzard
尽管我很想采用这种解决方法,但正如Zabuza所说,这些东西的问题就在于此。如果我们开始遵守规则,一切都会慢慢崩溃。这不起作用的原因是有充分的理由的。 - cvekaso
像DomainKeys和服务记录这样的系统使用下划线作为一种方式来确保它们的特殊字符不会与主机名混淆。 - cvekaso
虽然主机名可能不包含其他字符,例如下划线字符(_),但其他 DNS 名称可能包含下划线。 - fg78nc
1
如果你这样做,你会得到一个 null 作为主机。 - fg78nc

4

这个 bug 不在 Java 代码中,而是出现在主机名命名上,因为下划线不是有效的主机名字符。虽然被广泛不正确地使用,但 Java 拒绝处理这样的主机名。


这个 /5-12-145-35_s-81:443 是合法的。 - user10871691

0

我遇到了类似的问题,并通过识别主机的地址而不是名称来解决它。

InetAddress inetAddress = InetAddress.getLocalHost();
// detectedHostName = inetAddress.getHostName(); // returns 5-12-145-35_s-8
detectedHostName = inetAddress.getHostAddress(); // returns x.x.x.x

getHostAddress 的结果可以安全地传递给 URI 解析器。


我明白这可能并不是每种情况的有效替代方案。


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