Java:将“host:port”验证和转换为InetSocketAddress的常见方法是什么?

37
在Java中,将形如host:port的字符串验证并转换为InetSocketAddress实例的常见方法是什么?
最好遵循以下标准:
  • 无地址查找;

  • 适用于IPv4、IPv6和“字符串”主机名;
    (对于IPv4,它是ip:port,对于IPv6,它是[ip]:port,对吗?是否有定义所有这些方案的某些RFC?)

  • 最好不手动解析字符串。
    (我在考虑所有那些特殊情况,当有人认为他知道套接字地址的所有有效形式,但忘记了“那个特殊情况”,这会导致意外结果。)


你只需要IP地址吗?还是也需要主机名? - PSpeed
主机和IP地址,但不进行任何查找。这些东西不仅在“在线上下文”中有意义。1.1.1.1:123是一个有效的互联网套接字地址,my.host.com:80也是,[::1]:456也是。 - java.is.for.desktop
主机解析为IP地址。 - AFK
主机只有在你允许的情况下才会解析为IP地址 ;) - java.is.for.desktop
9个回答

58

我本人提出一个可能的解决方案。

将一个字符串转换为URI(这将自动验证它),然后查询URI的主机和端口组件。

遗憾的是,带有主机组件的URI必须具有方案。这就是为什么这个解决方案“不完美”的原因。

String string = ... // some string which has to be validated

try {
  // WORKAROUND: add any scheme to make the resulting URI valid.
  URI uri = new URI("my://" + string); // may throw URISyntaxException
  String host = uri.getHost();
  int port = uri.getPort();

  if (uri.getHost() == null || uri.getPort() == -1) {
    throw new URISyntaxException(uri.toString(),
      "URI must have host and port parts");
  }

  // here, additional checks can be performed, such as
  // presence of path, query, fragment, ...


  // validation succeeded
  return new InetSocketAddress (host, port);

} catch (URISyntaxException ex) {
  // validation failed
}

这个解决方案不需要自定义字符串解析,适用于IPv41.1.1.1:123),IPv6[::0]:123)和主机名my.host.com:123)。

恰好,这个解决方案非常适合我的场景。我本来就打算使用URI方案。


3
请注意,它还可以与许多其他东西一起使用,例如:"my://foo:bar:baz/"或"my://something@foo.bar:8000/"等等......这可能对你的情况有或没有问题,但并不能真正满足原始问题的愿望,即避免导致“意外结果”的事情。 :) - PSpeed
1
这段代码在没有端口的IPv6地址上无法正常工作。 String host = "fc00::142:10"; System.out.println(new URI("my://" + host).getHost()); 这段代码将打印null。 - Dikla

7
正则表达式可以很好地完成这个任务:
Pattern p = Pattern.compile("^\\s*(.*?):(\\d+)\\s*$");
Matcher m = p.matcher("127.0.0.1:8080");
if (m.matches()) {
  String host = m.group(1);
  int port = Integer.parseInt(m.group(2));
}

您可以通过多种方式来实现,例如将端口设置为可选项或对主机进行一些验证。

注意,在这种情况下,'^'和'$'是不必要的,因为matches()必须匹配整个字符串。 - PSpeed
该模式过于宽松,即使前导和尾随的空格被删除。例如,端口部分必须在0到65535之间,零只能在0的情况下使用。仅针对端口部分的模式是这个可怕的东西:"((0)|([1-9]\\d{0,2})|([1-5]\\d{3})|([6-9]\\d{3})|([1-5]\\d{4})|(6[0-4]\\d{3})|(65[0-4]\\d{2})|(655[0-2]\\d)|(6553[0-5]))" - Urhixidur

7

虽然这个答案并没有直接回答问题,但对于像我一样只想解析主机和端口而不一定是完整的 InetAddress 的人来说,这个答案仍然可能有用。Guava 有一个 HostAndPort 类,其中包含一个 parseString 方法。


1
请注意Guava的HostAndPort。它不会对主机名进行严格验证。请查看其类文档或源代码。 - stanleyxu2005
我也注意到HostAndPort非常有用,但我发现自己仅仅为了验证主机而调用InetAddresses.forString(hostIp)。 HostAndPort验证端口没问题,但不验证主机,这毫无意义! - titania424

5

另一位用户已经给出了一个正则表达式的答案,这也是我在最初询问有关主机的问题时要做的。 我仍然会这样做,因为这是一个稍微高级一些的正则表达式的示例,可以帮助确定您正在处理哪种类型的地址。

String ipPattern = "(\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}):(\\d+)";
String ipV6Pattern = "\\[([a-zA-Z0-9:]+)\\]:(\\d+)";
String hostPattern = "([\\w\\.\\-]+):(\\d+)";  // note will allow _ in host name
Pattern p = Pattern.compile( ipPattern + "|" + ipV6Pattern + "|" + hostPattern );
Matcher m = p.matcher( someString );
if( m.matches() ) {
    if( m.group(1) != null ) {
        // group(1) IP address, group(2) is port
    } else if( m.group(3) != null ) {
        // group(3) is IPv6 address, group(4) is port            
    } else if( m.group(5) != null ) {
        // group(5) is hostname, group(6) is port
    } else {
        // Not a valid address        
    }
}

修改使端口号变为可选项非常简单。将“:(\d+)”改为“(?::(\d+))?”,然后检查第二组是否为空等即可。
编辑:我要注意的是,我不知道有什么“常见方式”,但如果必须这样做,上述方法是我会使用的方法。
另外请注意:如果主机和IPv4情况实际上会被处理相同,则可以删除IPv4情况。我将它们分开是因为有时如果您知道IP地址,可以避免最终的主机查找。

2
new InetSocketAddress(
  addressString.substring(0, addressString.lastIndexOf(":")),
  Integer.parseInt(addressString.substring(addressString.lastIndexOf(":")+1, addressString.length));

我可能犯了一些小错误。我假设你只想从以此格式的字符串中获取一个新的InetSocketAddress对象。主机:端口


5
这个会在IPv6上失败,因为它类似于[2001:db8:85a3::8a2e:370:7334]:80 - java.is.for.desktop
也许我的问题不太正确。IP地址也是主机吗?我不知道。 - java.is.for.desktop
你说得对,这对IPv6会失败。此外,主机地址是一个IP地址。我想你只需要在这个语句之前加上一个if语句,并根据IP地址是v6还是v4创建一个InetSocket。 - AFK
2
...或者使用lastIndexOf()代替indexOf()......虽然我不知道InetSocketAddress对IPv6有什么期望。 - PSpeed
请注意:post strong 应该是 lastIndexOf(':') + 1,不需要第二个参数。 - PSpeed
如果没有提供端口号,它会在IPv6地址上失败。例如:"[2001:db8:85a3::8a2e:370:7334]",因此如果您要将此代码应用于用户提供的输入,则不是一个好主意。当您考虑到如果端口号没有硬编码,这几乎肯定是情况时,这一点就显而易见了。 - Robin Davies

0

URI 可以实现这个功能:

URI uri = new URI(null, "example.com:80", null, null, null);

很不幸,当前的OpenJDK(或文档)存在一个错误,即未正确验证权限。文档说明如下:

然后将生成的URI字符串解析,就像调用URI(String)构造函数并在结果上调用parseServerAuthority()方法一样

不幸的是,这个parseServerAuthority的调用没有发生,因此真正的解决方案是进行适当的验证:

URI uri = new URI(null, "example.com:80", null, null, null).parseServerAuthority();

那么

InetSocketAddress address = new InetSocketAddress(uri.getHost(), uri.getPort());

0

在编程中,有各种各样奇特的hackery和优雅但不安全的解决方案。有时候,不太优美但却直截了当的蛮力解决方案是可行的。

public static InetSocketAddress parseInetSocketAddress(String addressAndPort) throws IllegalArgumentException {
    int portPosition = addressAndPort.length();
    int portNumber = 0;
    while (portPosition > 1 && Character.isDigit(addressAndPort.charAt(portPosition-1)))
    {
        --portPosition;
    }
    String address;
    if (portPosition > 1 && addressAndPort.charAt(portPosition-1) == ':')
    {
        try {
            portNumber = Integer.parseInt(addressAndPort.substring(portPosition));
        } catch (NumberFormatException ignored)
        {
            throw new IllegalArgumentException("Invalid port number.");
        }
        address = addressAndPort.substring(0,portPosition-1);
    } else {
        portNumber = 0;
        address = addressAndPort;
    }
    return new InetSocketAddress(address,portNumber);
}

0

开源 IPAddress Java 库有一个HostName类,可以完成所需的解析。免责声明:我是 IPAddress 库的项目经理。

它将解析IPv4、IPv6和带或不带端口的字符串主机名。它将处理各种格式的主机和地址。顺便说一下,这并没有单独的RFC标准,有许多RFC以不同的方式适用。

String hostName = "[a:b:c:d:e:f:a:b]:8080";
String hostName2 = "1.2.3.4:8080";
String hostName3 = "a.com:8080";
try {
    HostName host = new HostName(hostName);
    host.validate();
    InetSocketAddress address = host.asInetSocketAddress();
    HostName host2 = new HostName(hostName2);
    host2.validate();
    InetSocketAddress address2 = host2.asInetSocketAddress();
    HostName host3 = new HostName(hostName3);
    host3.validate();
    InetSocketAddress address3 = host3.asInetSocketAddress();
    // use socket address      
} catch (HostNameException e) {
    String msg = e.getMessage();
    // handle improperly formatted host name or address string
}

0
使用番石榴
    String[] examples = new String[] {"192.168.0.1:456", "[2010:836B:4179::836B:4179]:456", "2010:836B:4179::836B:4179", "g35v4325234f:446", "sagasdghdfh"};

    for (String example : examples) {
        HostAndPort hostAndPort = HostAndPort.fromString(example);
        String host = hostAndPort.getHost();
        Log.debug(host);

        if (InetAddresses.isInetAddress(host)) {
            InetAddress inetAddress = InetAddresses.forString(host);
            try {
                Log.debug("is reachable: " + inetAddress.isReachable(500));
            } catch (IOException e) {
                Log.error(e);
            }
        }
    }

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