host:port
Java 中验证表单字符串并将其转换为 的实例的常用方法是InetSocketAddress
什么?
如果满足以下条件,那就太好了:
没有地址查找;
适用于 IPv4、IPv6 和“字符串”主机名;
(对于 IPv4 是ip:port
,对于 IPv6 是[ip]:port
,对吗?是否有一些 RFC 定义了所有这些方案?)最好不要手动解析字符串。
(我正在考虑所有这些特殊情况,当有人认为他知道所有有效形式的套接字地址时,却忘记了导致意外结果的“那种特殊情况”。)
host:port
Java 中验证表单字符串并将其转换为 的实例的常用方法是InetSocketAddress
什么?
如果满足以下条件,那就太好了:
没有地址查找;
适用于 IPv4、IPv6 和“字符串”主机名;
(对于 IPv4 是ip:port
,对于 IPv6 是[ip]:port
,对吗?是否有一些 RFC 定义了所有这些方案?)
最好不要手动解析字符串。
(我正在考虑所有这些特殊情况,当有人认为他知道所有有效形式的套接字地址时,却忘记了导致意外结果的“那种特殊情况”。)
我自己提出了一种可能的解决方法。
将字符串转换为 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
}
此解决方案不需要自定义字符串解析,适用于IPv4 ( 1.1.1.1:123
)、IPv6 ( [::0]:123
) 和主机名( my.host.com:123
)。
意外的是,这个解决方案非常适合我的场景。无论如何,我打算使用 URI 方案。
一个正则表达式会非常巧妙地做到这一点:
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));
}
您可以通过多种方式实现这一点,例如使端口成为可选端口或在主机上进行一些验证。
它并没有准确回答这个问题,但是这个答案对于像我这样只想解析主机和端口但不一定是完整的InetAddress
. Guava 有一个带有方法的HostAndPort类parseString
。
另一个人给出了一个正则表达式的答案,这是我最初询问有关主机的问题时正在做的事情。我仍然会这样做,因为它是一个稍微高级一点的正则表达式示例,可以帮助确定您正在处理的地址类型。
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+))?" 然后检查组(2)等是否为空。
编辑:我会注意到没有我知道的“通用方式”方式,但以上是我必须这样做的方式。
另请注意:如果实际处理主机和 IPv4 情况相同,则可以删除 IPv4 情况。我把它们分开是因为如果你知道你有 IP 地址,有时你可以避免最终的主机查找。
new InetSocketAddress(
addressString.substring(0, addressString.lastIndexOf(":")),
Integer.parseInt(addressString.substring(addressString.lastIndexOf(":")+1, addressString.length));
? 我可能犯了一些愚蠢的小错误。而且我假设您只是想要一个新的 InetSocketAddress 对象,该对象仅采用该格式的字符串。主机:端口
其他地方提供的各种奇特的骇客、优雅但不安全的解决方案。有时,不雅的蛮力解决方案就是方法。
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);
}
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());
开源 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
}