1

What I want to achieve :

I have a JAVA code which starts an ngrok(ngrok is used to expose your local ports to web so others can access it. More info here) process. Now after spawning new ngrok process using Runtime.getRuntime(), I want to read it's output.

Problem :

When I try to read from InputStream using getInputStream() and getErrorStream(), my program gets stuck there. So I'm unable to read output of process.

Code :

Process ngrokProcess = Runtime.getRuntime().exec(" cmd /k start cmd /k \" ngrok http 8080 \" ");
    
String collect = Stream.of(ngrokProcess.getInputStream(), ngrokProcess.getErrorStream()).parallel().map((InputStream is) -> {
                StringBuilder output = new StringBuilder();
                try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
                    String line;
                    while ((line = br.readLine()) != null) {
                        output.append(line);
                        output.append("\n");
                    }
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
                return output;
            }).collect(Collectors.joining());

What am I doing wrong here? Or is there any other way in which I can achieve it?

I can't wait for process to finish as it's never ending unless terminated forcefully. Also, I can't use ProcessBuilder as command prompt is not visible to user (I can be wrong here and need to tweak some things for it to visible. If it's possible let me know).

If more info is required let me know.

Thanks in advance :)

semicolon
  • 487
  • 2
  • 7

1 Answers1

0

There are a few things you should check / revise.

Runtime.getRuntime() USES ProcessBuilder and it is always better to use ProcessBuilder directly as you have more control over the launch - especially file redirection or STDERR to STDOUT switching.

The command you execute is CMD, which calls CMD as new process which calls ngrok. Because of the sub-processes, the stdout/stderr streams won't be for ngrok.

So try to bypass the unnecessary CMD by resolving the path to ngrok in CMD.EXE:

where ngrok

Define the call as String[]

String[] cmd = new String[] {"/path/to/ngrok", "http", "8080"  }
Process ngrokProcess = Runtime.getRuntime().exec(cmd);

Your command may launch now but another issue you may hit is that if you don't consume the output fast enough it will block the Process. You can re-direct the output to files or add a thread to consume the streams.

I've just posted a Launch class in another SO question which shows how to execute command with file redirects. See Launch.exec(cmd).

However running background threads to consume STDOUT/ERR is better for tasks that run for a long time as those background threads can be scanning the output as it runs, whereas file redirection is easier for short runs and to post-process once the process is ended. I your app needs to continue you should have background thread do the waitFor().

DuncG
  • 12,137
  • 2
  • 21
  • 33
  • Hi @DuncG, thanks for help, but I'm using Runtime instead of ProcessBuilder because I want user to see cmd terminal in foreground, which I was not able to get in ProcessBuilder. Is there any way to do so? – semicolon Jul 22 '20 at 05:50
  • Another solution may be to run `conhost.exe` to launch new window and use `process.getOutStream()` to send the commands to it. See answer here: [https://stackoverflow.com/questions/63244536/how-to-display-the-contents-of-a-txt-file-on-cmd-window-using-java/63247975#63247975] – DuncG Aug 08 '20 at 15:23
  • Ok @DuncG, I will check it out. – semicolon Sep 22 '20 at 07:17