views:

450

answers:

4

Hello, everyone!

What is the common way in Java to validate and convert a string of the form host:port into an instance of InetSocketAddress?

It would be nice if following criteria were met:

  • No address lookups;

  • Working for IPv4, IPv6, and "string" hostnames;
    (For IPv4 it's ip:port, for IPv6 it's [ip]:port, right? Is there some RFC which defines all these schemes?)

  • Preferable without parsing the string by hand.
    (I'm thinking about all those special cases, when someone think he knows all valid forms of socket addresses, but forgets about "that special case" which leads to unexpected results.)

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

? I probably made some little silly mistake. and I'm assuming you just wanted a new InetSocketAddress object out of the String in only that format. host:port

AFK
this would fail for IPv6, because it is something like `[2001:db8:85a3::8a2e:370:7334]:80`
java.is.for.desktop
Perhaps my question is wrong. Is an IP address also a host? I don't know.
java.is.for.desktop
you're right this would fail for IPv6. Also the host's address is an IP Address. I guess you would just have to put in an if statement before this one and create an InetSocket based on whether or not the IP address is v6 or v4
AFK
...or use lastIndexOf() instead of indexOf()... Though I don't know what InetSocketAddress is expecting for IPv6.
PSpeed
Also note: the post strong should be lastIndexOf(':') + 1 and no second parameter is required.
PSpeed
+2  A: 

A regex will do this quite neatly:

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

You can this in many ways such as making the port optional or doing some validation on the host.

cletus
Note, the '^' and '$' are unnecessary in this case as matches() must match the entire string anyway.
PSpeed
+2  A: 

Another person has given a regex answer which is what I was doing to do when originally asking the question about hosts. I will still do because it's an example of a regex that is slightly more advanced and can help determine what kind of address you are dealing with.

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 address
    } else {
        // Not a valid address        
    }
}

Modifying so that port is optional is pretty straight forward. Wrap the ":(\d+)" as "(?::(\d+))" and then check for null for group(2), etc.

Edit: I'll note that there's no "common way" way that I'm aware of but the above is how I'd do it if I had to.

Also note: the IPv4 case can be removed if the host and IPv4 cases will actually be handled the same. I split them out because sometimes you can avoid an ultimate host look-up if you know you have the IP address.

PSpeed
+1  A: 

I myself propose one possible workaround solution.

Convert a string into URI (this would validate it automatically) and then query the URI's host and port components.

Sadly, an URI with a host component MUST have a scheme. This is why this solution is "not perfect".

String string = ... // some string which has to be validated
String host;
int port;

try {
  // WORKAROUND: add any scheme to make the resulting URI valid.
  URI uri = new URI("my://" + string); // may throw URISyntaxException
  host = uri.getHost();
  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, ...

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

// validation succeeded

This solution needs no custom string parsing, works with IPv4 (1.1.1.1:123), IPv6 ([::0]:123) and host names (my.host.com:123).

Accidentally, this solution is well suited for my scenario. I was going to use URI schemes anyway.

java.is.for.desktop
Note that is also works with quite a few others things, e.g.: "my://foo:bar:baz/" or "my://[email protected]:8000/" and so on... which may or may not be a problem in your case but doesn't really satisfy the original question's desire to avoid things that "lead to unexpected results". :)
PSpeed
Thanks, I'm aware of that ;) Added comment to code.
java.is.for.desktop