0

I have a larger block of Java code, but the important lines are these:

public static String tcp(String hostName, Number port, Number connectionTimeOutMs, Number readTimeOutMs, String message) {
    String errmsg = "";
    try (
        Socket socket = new Socket();
    ) {
        Inet4Address address = (Inet4Address) Inet4Address.getByName(hostName);
        System.out.println("IP address:" + address.getHostAddress());
        socket.connect(new InetSocketAddress(address, port.intValue()), connectionTimeOutMs.intValue());
        socket.setSoTimeout(readTimeOutMs.intValue());

When I supply an IP address in the form "45.79.112.203" or "tcpbin.com", the code gives a SocketTimeoutException.

In the latter case, the line

System.out.println("IP address:" + address.getHostAddress());

gives the correct IP address, so the hostname is resolved correctly; it matches what ping tcpbin.com returns.

I want to be able to call the function with either an IPv4 address (in String format) or a hostname.

What am I doing wrong? Why does the socket fail to establish a connection, even with a high timeout of 60,000 ms?

Notes:

tcpbin.com is an "echo" server to test socket connections. It is only used as an example and should not be the cause of the problem.

Try the following:

echo "Text to send to TCP" | nc tcpbin.com 4242

You should get back the string that was just sent.

In the tcp() function, I pass in numbers in the form of a Number object, since the Java code gets called from Karate test framework via Java inter-op and JavaScript. JavaScript has the type Number, but no int or double.

===

Update:

Here a simple tcp server TcpServer.java

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServer {
    public static void main(String[] args) {
        System.out.println("Listening on port 4242");
        ServerSocket listener = null;
        try {
            do {
                listener = new ServerSocket(4242);
                Socket other = listener.accept();
                System.out.println(">>> got a new connection from "
                        + other.getInetAddress().toString() + " <<<");
                other.getOutputStream().write("Blah blah".getBytes());
                other.close();
                listener.close();
            } while (true);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

===

Here a test class to test the tcp() function. It is the connect() statement that times out in case host != localhost.

TestTcpFunction.java:

import java.io.*;
import java.net.*;

public class TestTcpFunction {
    public static void main(String[] args) {
        String sendMessage = "Blah blah";

        String host = (args.length==0)
                    ? "localhost"
                    : "tcpbin.com";

        String result = tcp(host, 4242, 30000, 30000, sendMessage);
        System.out.println("result = " + result);
        System.out.println("matches = " + result.equals(sendMessage));
    }

    public static String tcp(String hostName, Number port, Number connectionTimeOutMs, Number readTimeOutMs, String message) {
        String errmsg = "";
        try (
            Socket socket = new Socket();
        ) {
            Inet4Address address = (Inet4Address) Inet4Address.getByName(hostName);
            System.out.println("trying to connect to:" + address.getHostAddress());
            socket.connect(new InetSocketAddress(address, port.intValue()), connectionTimeOutMs.intValue()); // <<< times out if not localhost
            socket.setSoTimeout(readTimeOutMs.intValue());

            try (
                PrintWriter out = new PrintWriter(socket.getOutputStream(), true); // autoflush
                BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            ) {
                out.print(message);
                StringBuilder sb = new StringBuilder();
                String line;
                boolean addNewline = false;

                while ((line = in.readLine()) != null) {
                    if (addNewline)
                        sb.append('\n');
                    sb.append(line);
                    if (line.lastIndexOf("</response>") >= 0)
                        break;

                    addNewline = true;
                }

                return sb.toString(); // The xml may not be well formed, for instance missing </response>
            } finally {}

        } catch (UnknownHostException e) {
            errmsg = "Unknown host " + hostName;
        } catch (SocketTimeoutException e) {
            errmsg = "Socket connection timeout (before connection got established)";
        } catch (SocketException e) {
            errmsg = "Socket error: " + e.getMessage();
        } catch (IOException e) {
            errmsg = "Couldn't get I/O for the connection to " + hostName;
        } catch (Exception e) {
            errmsg = "Unknown socket error " + e.getMessage();
        }

        System.err.println(errmsg);
        return "<Error> function tcp (Utils.java): " + errmsg + "</Error>";
    }
}

===

Compile both with javac. Then start the server with java TcpServer. Next run java TestTcpFunction in a different shell, without parameters.

The first time (with local host) it should work correctly. Then run again, but with any parameter(s), like java TestTcpFunction 1 This time I get a timeout while trying to connect.

The code has been build and tested on my machine.

user2943111
  • 421
  • 1
  • 5
  • 15
  • Please reduce your code to the absolute minimum necessary for others to reproduce your problem. Currently it is just a function which gets used in some unknown context - and this context might be the cause of your problems. Also make sure that the site and port in question is actually reachable from the system where you run this code on. – Steffen Ullrich Nov 24 '20 at 23:20
  • I just made a very simple server in Java, that returns "Blah blah". When calling previously mentioned tcp Java function with "localhost", it works. This (plus some not shown debug System.out.println statements) also proves that karate and the Java Interop is not the problem. Note that the bash command: echo "Text to send to TCP" | nc tcpbin.com 4242 works without problems, so no firewall issues. The host and port are reachable from my system. (netcat needs to be installed). OS: Ubuntu 16.04 LTS – user2943111 Nov 24 '20 at 23:55
  • I just added a simple server and the whole class to test the tcp function. They are at the bottom in the main article. – user2943111 Nov 25 '20 at 01:11
  • What happens when you don't specify a timeout? Does it eventually connect or return an error? – rveerd Nov 25 '20 at 16:06

1 Answers1

0

The client does not time out in connect. A simple output after connect shows that the connection is actually successfully:

        socket.connect(new InetSocketAddress(address, port.intValue()), connectionTimeOutMs.intValue()); // <<< times out if not localhost
        System.out.println("connected successfully");

Instead the program hangs while reading from the server. With the current code it will wait until the server closes the connection or has send a line with </response>. But the server tcpbin.com:4242 will not do anything like this. It will simply read anything and echo it back. To get a </response> string one actually has to send this string - which is not done.

Because of this the read will time out after a while based on the timeout set with socket.setSoTimeout. The resulting SocketTimeoutException is wrongly interpreted as connection timeout, but it is a read timeout.

Given that the code expects the echoed message to include the string </response> one must add it to the sent message:

    String sendMessage = "Blah blah</response>";

This is still not enough though and a tcpdump shows that the message does not even get sent. This is because the expectation, that out.print(message); is affected by the autoflush is simply wrong - see I created a PrintWriter with autoflush on; why isn't it autoflushing?. Thus, one must explicitly flush the writer:

            out.print(message);
            out.flush();

tcpdump shows that the message is now actually send, but nothing is echoed back. This is because the echo server actually expects to read lines, but no line end was send yet. Adding it actually helps to send the message, get an echoed message back and break out of the loop:

    String sendMessage = "Blah blah</response>\n";

And why did it work with localhost? Because the sample server did not actually behave like the echo server at tcpbin.com. It did not read anything but just sent a fixed message back and closed the connection.

Steffen Ullrich
  • 114,247
  • 10
  • 131
  • 172
  • Thank you very much. Of course, the echo server is a 'placeholder' for an actual tcp server, that returns answers between tags. I prefer to develop on my laptop and avoid delays by working remotely on the company server. I will install tcpdump, apply your fixes (SocketTimeoutException, flush, perhaps add \n in sent messages, add the print statement about the connection). – user2943111 Nov 25 '20 at 19:55