Issue
Okay so, I'm having a problem where the response from the shell is very inconsistent. The welcome message for example, can come in 2 separate responses. And for some reason, when I use any command after that, the prompt will show up twice in my response.
I've noticed that these problems only occur when using my own piped input/output streams. When I use System.in
and System.out
, these problems don't occur at all.
Here's a picture where I use System.in
and System.out
:
When using my own piped input/output streams:
Below the related code.
SSHConnection.java
package com.displee.ssh;
import com.jcraft.jsch.*;
import java.util.function.Function;
public class SSHConnection {
private static final JSch JSCH_INSTANCE = new JSch();
private Session session;
private ShellSession shellSession;
public SSHConnection(String ip, String username, String password) {
try {
this.session = JSCH_INSTANCE.getSession(username, ip);
this.session.setPassword(password);
this.session.setConfig("StrictHostKeyChecking", "no");
} catch(Exception e) {
e.printStackTrace();
this.session = null;
}
}
public boolean connect() {
try {
session.connect();
return true;
} catch(Exception e) {
e.printStackTrace();
session.disconnect();
return false;
}
}
public boolean isConnected() {
return session.isConnected();
}
public void disconnect() {
session.disconnect();
}
public boolean startShellSession(Function<String, Void> callback) {
try {
shellSession = new ShellSession();
shellSession.start(session, callback);
return true;
} catch(Exception e) {
e.printStackTrace();
return false;
}
}
public void writeShellMessage(String message) {
shellSession.write(message + "\r\n");
}
}
ShellSession.java
package com.displee.ssh;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelShell;
import com.jcraft.jsch.Session;
import java.io.*;
import java.util.function.Function;
/**
* A class representing a shell session.
* @author Displee
*/
public class ShellSession {
/**
* If this session is running.
*/
private boolean running = true;
private PipedOutputStream poutWrapper;
private PipedInputStream pin = new PipedInputStream(4096);
private PipedInputStream pinWrapper = new PipedInputStream(4096);
private PipedOutputStream pout;
private String lastCommand;
/**
* Constructs a new {@code ShellSession} {@code Object}.
*/
public ShellSession() {
try {
pout = new PipedOutputStream(pinWrapper);
poutWrapper = new PipedOutputStream(pin);
} catch(Exception e) {
e.printStackTrace();
}
}
/**
* Start this shell session.
* @param session The ssh session.
*/
public void start(Session session, Function<String, Void> callback) {
ChannelShell shellChannel = null;
try {
shellChannel = (ChannelShell) session.openChannel("shell");
shellChannel.setInputStream(pin);
shellChannel.setOutputStream(pout);
shellChannel.connect();
} catch(Exception e) {
e.printStackTrace();
}
if (shellChannel == null) {
return;
}
final Channel channel = shellChannel;
Thread thread = new Thread(() -> {
while (running) {
try {
if (pinWrapper.available() != 0) {
String response = readResponse();
callback.apply(response);
}
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
channel.disconnect();
});
thread.setDaemon(true);
thread.start();
}
/**
* Stop this shell session.
*/
public void stop() {
running = false;
}
/**
* Send a message to the shell.
* @param message The message to send.
*/
public void write(String message) {
lastCommand = message;
try {
poutWrapper.write(message.getBytes());
} catch(IOException e) {
e.printStackTrace();
}
}
/**
* Read the response from {@code pinWrapper}.
* @return The string.
* @throws IOException If it could not read the piped stream.
*/
private synchronized String readResponse() throws IOException {
final StringBuilder s = new StringBuilder();
while(pinWrapper.available() > 0) {
s.append((char) pinWrapper.read());
}
return s.toString();
}
}
My JavaFX controller:
package com.displee.ui;
import com.displee.ssh.SSHConnection;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import java.net.URL;
import java.util.ResourceBundle;
public class Controller implements Initializable {
@FXML
private TextArea console;
@FXML
private TextField commandInput;
@Override
public void initialize(URL location, ResourceBundle resources) {
SSHConnection connection = new SSHConnection("host", "username", "password");
connection.connect();
connection.startShellSession(s -> {
console.appendText(s);
return null;
});
commandInput.setOnAction(x -> {
connection.writeShellMessage(commandInput.getText());
commandInput.clear();
});
}
}
Complete code: https://displee.com/upload/SSHProject.zip
I tried increasing the size of my inputstreams, but that didn't work.
This problem is really frustrating me. If anyone know how I can fix my problem, I'd really appreciate it.
Note: I've also noticed that it's pretty slow when using my piped streams. How can I speed this up?
Solution
Are you correctly processing CR (carriage return)?
The server probably sends the prompt twice in all cases. But if there's CR between the two prompts, when printed on System.out
the second prompt will overwrite the first. While if your stream interprets the CR as a "new-line" (=line feed), you get two prompts.
Answered By - Martin Prikryl