使用Java.net.URI获取带有下划线的主机名

15

我发现这个方法的行为很奇怪:

import java.net.URI

    URI url = new URI("https://pmi_artifacts_prod.s3.amazonaws.com");
    System.out.println(url.getHost()); /returns NULL
    URI url2 = new URI("https://s3.amazonaws.com");
    System.out.println(url2.getHost());  //returns s3.amazonaws.com

我希望url.getHost()的值是pmi_artifacts_prod.s3.amazonaws.com,但是它返回了NULL。后来发现问题出在域名中的下划线,这是一个已知的bug,但我必须确切地使用这个主机。有什么办法可以解决这个问题吗?


这里有一篇很棒的文章 https://blogs.wandisco.com/java-and-underscores-in-host-names/ 简而言之,是的,你可以这样做(有点)...但你真的不应该这样做。 - Doug
3个回答

10

这个问题并不是Java的问题,而是在给主机命名时使用了下划线,因为下划线在主机名中是无效字符。虽然很多人错误地使用了下划线,但Java会拒绝处理这样的主机名。

https://en.wikipedia.org/wiki/Hostname#Restrictions_on_valid_hostnames

一个可能的解决方法:

public static void main(String...a) throws URISyntaxException, NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
    URI url = new URI("https://pmi_artifacts_prod.s3.amazonaws.com");
    System.out.println(url.getHost()); //NULL


    URI uriObj = new URI("https://pmi_artifacts_prod.s3.amazonaws.com");
    if (uriObj.getHost() == null) {
        final Field hostField = URI.class.getDeclaredField("host");
        hostField.setAccessible(true);
        hostField.set(uriObj, "pmi_artifacts_prod.s3.amazonaws.com");
    }
    System.out.println(uriObj.getHost()); //pmi_artifacts_prod.s3.amazonaws.com


    URI url2 = new URI("https://s3.amazonaws.com");
    System.out.println(url2.getHost());  //s3.amazonaws.com
}

14
"在发送内容时要保守,接受内容时要开放。如果人们在主机名中使用下划线,一个全球使用的库应该处理它们,而不是失败。这简单来说就是不够健壮,对于 Java 这样一种广泛使用的语言来说,这是一个令人震惊的糟糕决定。" - Keith Tyler
1
我不是非常清楚这个是否有效。当然,我可以验证Java确实会这样做,但我不明白Java8在2014年发布时为什么会有这个限制。据说,RFC2181撤销了这个限制,并于1997年提出(尽管公平地说,我不知道它何时获得批准)。如果2181撤销了它,那么为什么Java仍然不允许它? - searchengine27
1
The DNS itself places only one restriction on the particular labels that can be used to identify resource records. That one restriction relates to the length of the label and the full name - searchengine27

2

可以通过打补丁将下划线支持直接添加到URI中:

public static void main(String[] args) throws Exception {
    patchUriField(35184372088832L, "L_DASH");
    patchUriField(2147483648L, "H_DASH");
    
    URI s = URI.create("http://my_favorite_host:3892");
    // prints "my_favorite_host"
    System.out.println(s.getHost());
}

private static void patchUriField(Long maskValue, String fieldName)
        throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, NoSuchFieldException {
        Field field = URI.class.getDeclaredField(fieldName);
        
        Field modifiers = Field.class.getDeclaredField("modifiers");
        modifiers.setAccessible(true);
        modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
        
        field.setAccessible(true);
        field.setLong(null, maskValue);
}

2
在Java 10中不再起作用,因为URI类中的lowMaskhighMask私有方法已被删除。 - Lukasz Frankowski
@ŁukaszFrankowski 感谢您指出!我已经将它适配到JDK10。 - Nikita Koksharov

1
请注意,尽管如此,保留HTML标签。
new URI("https://pmi_artifacts_prod.s3.amazonaws.com");

这种情况下,代码不会抛出异常,并且@Vurtatoo提供的解决方法可以解决问题。但是,它无法处理诸如https://a_b?c={1}之类的url。

我还发现

new URI("https://a_b?c={1}")

将会抛出异常但是

new URI("https://a_b?c=1")

我不确定为什么会这样,但我的理解是我们不应该对Java URI类的实现细节做任何假设。如果必须使用Java URI,则最好分叉源代码并进行所需更改。

won't.


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