SSH Over Java
Recently I’ve had the need from java to run a job on a remote server that can take a variable amount of time (anywhere from a few seconds to a few hours) and needed to detect when it is complete and get a file it generated when completed. This was a little challanging at first, as I had never used ssh from java, but I knew a little searching would yield results.
After a little searching, I found Java Secure Channel (JSch), a handy library with a BSD license and used in Ant and eclipse. The only sad thing is that there is zero documentation outside of a few examples, and my knowledge of streams (specifically PipedInputStream) was sorely behind as I hadn’t used them directly, since, well, college.
A little confusion abounded for awhile … how the heck am I supposed to detect the end of data coming over a pipe!? I mean, you can detect when it closes, but in my case I want to stay connected and issue more commands and close when I please, which means I cannot simply detect when the pipe is closed.
I struggled for a bit, then decided … I’ll just append ” && echo
public void send(String command) throws IOException {
command += " && echo \"" + TERMINATOR + "\"\n";
toServer.write(command.getBytes());
lastCommand = new String(command);
}
Getting the server response was a bit tricky. Since the command shows up when reading from the server, I need to detect the 2nd time it shows up and break out of the loop on that. After a little time I hacked up the following:
public String getServerResponse() throws IOException, InterruptedException {
StringBuilder builder = new StringBuilder();
int count = 0;
String line = "";
for (int i = 0; true; i++) {
line = fromServer.readLine();
builder.append(line).append("\n");
if (line.contains(TERMINATOR) && (++count > 1)) {
break;
}
}
String result = builder.toString();
int beginIndex = result.indexOf(TERMINATOR+"\"") + ((TERMINATOR+"\"").length());
result = result.substring(beginIndex);
return result.replaceAll(escape(TERMINATOR), "").trim();
}
This makes so many assumptions, but I guess that as I keep working on it I can find a better way. Anyway, below is the final result of the SshClient class I came up with:
package jamesstuff.ssh;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.regex.Pattern;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.UserInfo;
public class SshClient {
BufferedReader fromServer;
Pattern alphaNumeric = Pattern.compile("([^a-zA-z0-9])");
OutputStream toServer;
String lastCommand = "";
Channel channel;
private static final String TERMINATOR = "zDonez";
public void connect(String username, String password, String host, int port)
throws JSchException, IOException {
JSch shell = new JSch();
Session session = shell.getSession(username, host, port);
MyUserInfo ui = new MyUserInfo();
ui.setPassword(password);
session.setUserInfo(ui);
session.connect();
channel = session.openChannel("shell");
fromServer = new BufferedReader(new InputStreamReader(channel.getInputStream()));
toServer = channel.getOutputStream();
channel.connect();
if(isConnected()){
send("echo \"\"");
}
}
public boolean isConnected() {
// TODO Auto-generated method stub
return (channel != null && channel.isConnected());
}
public void disconnect() {
if (isConnected()) {
channel.disconnect();
}
}
public void send(String command) throws IOException {
command += "; echo \"" + TERMINATOR + "\"\n";
toServer.write(command.getBytes());
lastCommand = new String(command);
}
public String getServerResponse() throws IOException, InterruptedException {
StringBuilder builder = new StringBuilder();
int count = 0;
String line = "";
for (int i = 0; true; i++) {
line = fromServer.readLine();
builder.append(line).append("\n");
if (line.contains(TERMINATOR) && (++count > 1)) {
break;
}
}
String result = builder.toString();
int beginIndex = result.indexOf(TERMINATOR+"\"") + ((TERMINATOR+"\"").length());
result = result.substring(beginIndex);
return result.replaceAll(escape(TERMINATOR), "").trim();
}
private String escape(String subjectString){
return alphaNumeric.matcher(subjectString).replaceAll("\\\\$1");
}
private static class MyUserInfo implements UserInfo {
private String password;
public void setPassword(String password) {
this.password = password;
}
public String getPassphrase() {
// TODO Auto-generated method stub
return null;
}
public String getPassword() {
// TODO Auto-generated method stub
return password;
}
public boolean promptPassword(String arg0) {
// TODO Auto-generated method stub
return true;
}
public boolean promptPassphrase(String arg0) {
// TODO Auto-generated method stub
return true;
}
public boolean promptYesNo(String arg0) {
// TODO Auto-generated method stub
return true;
}
public void showMessage(String arg0) {
// TODO Auto-generated method stub
System.out.println(arg0);
}
}
}
If you're new here, you may want to subscribe to my RSS feed. Thanks for visiting!








November 14th, 2006 at 11:53 am
thanks for posting this code. the echo is a great idea.
April 11th, 2007 at 9:38 am
I don’t really understand where you will call the getServerResponse method ?
August 5th, 2007 at 4:59 am
Ah! At last an example which demystifies the JSch Shell IO streams! I have been trying to crack this one for the last two days as I have to do something very similar to what you are doing!
Thanks very much James!
October 30th, 2007 at 8:52 am
This script doesn’t execute the commands on the remote server. At least not when I’m testing it with something like this:
—
ssh.connect(”usr”, “pwd”, “host”, 22);
ssh.sendSimple(”myservice start”);
System.out.println(ssh.getServerResponse());
ssh.disconnect();
System.exit(0);
—
I’ve also used the Exec.java example in jsch and it works otherwise, but then the scripts won’t see the needed system variables and I don’t know how to convert it to using shell channel instead of exec…
May 20th, 2008 at 11:19 am
Thanks for the example! Curl does not support an SSH shell interface. I like your method for handling the response.
Thanks again
June 24th, 2008 at 7:59 am
What is the TERMILATOR doing?? Is it the command prompt?
I could not get the command prompt in “getServerResponse” method.
Can you plz let me know how to do that
June 24th, 2008 at 8:21 am
@dip TERMINATOR is simply a constant that defines the terminating string in the server output,used to indicate that output is done.
June 25th, 2008 at 9:24 am
Thanks James for your kind reply.
I am a new to Java and trying to implement SSH using your example.
I am using your code like—-
con.connect(”user”, “passwd”, “server”, 22);
System.out.println(con.getServerResponse());
if (con.isConnected()) {
con.send(”ls -l”);
}
System.out.println(con.getServerResponse());
But I am not receiving any response for the cmd “ls -l”
Plz let me know if my understanding is correct.
June 25th, 2008 at 9:27 am
I have put some traces in
line no 68. if (line.contains(TERMINATOR) && (++count > 1)) {
but it never executes this trace
September 13th, 2008 at 11:31 am
I think you need
toServer.flush()
after:
toServer.write(command.getBytes());
in your send method.
Also, not sure what the i counter in:
for (int i = 0; true; i++)
in your getServerResponse method is for?
Otherwise, your code was very helpful. Thank you. james