簡體   English   中英

Java:驗證並將“host:port”轉換為 InetSocketAddress 的常用方法?

[英]Java: Common way to validate and convert “host:port” to InetSocketAddress?

Java 中驗證和轉換形式為host:port的字符串為InetSocketAddress實例的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 有一個帶有parseString方法的HostAndPort類。

另一個人給出了一個正則表達式答案,這就是我最初詢問有關主機的問題時要做的事情。 我仍然會這樣做,因為它是一個稍微更高級的正則表達式示例,可以幫助確定您正在處理的地址類型。

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));

? 我可能犯了一些愚蠢的錯誤。 並且我假設您只需要該格式的 String 中的一個新 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);
}

開源 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
}

URI 可以做到這一點:

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

不幸的是,當前的 OpenJDK(或文檔中)存在一個錯誤,其中權限沒有得到正確驗證。 該文件指出:

然后通過調用 URI(String) 構造函數然后根據結果調用 parseServerAuthority() 方法來解析生成的 URI 字符串

不幸的是,對 parseServerAuthority 的調用並沒有發生,所以這里正確驗證的真正解決方案是:

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

然后

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM