Skip to content

Commit 409403c

Browse files
author
Juraj Veverka
committed
implemented OutputWriter to avoid direct writes to stdout and stderr streams
1 parent 96acc90 commit 409403c

File tree

12 files changed

+268
-121
lines changed

12 files changed

+268
-121
lines changed

ssh-server/ssh-examples/src/main/java/itx/examples/sshd/commands/SshClientCommandProcessor.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,25 @@
1212
import itx.examples.sshd.sessions.SshClientSessionListenerImpl;
1313
import itx.ssh.server.commands.CommandProcessor;
1414
import itx.ssh.server.commands.CommandResult;
15+
import itx.ssh.server.commands.OutputWriter;
1516
import itx.ssh.server.commands.subsystem.SshClientSession;
1617
import org.slf4j.Logger;
1718
import org.slf4j.LoggerFactory;
1819

1920
import java.io.IOException;
20-
import java.io.OutputStream;
2121
import java.nio.charset.Charset;
22-
import java.util.concurrent.CountDownLatch;
2322
import java.util.concurrent.ExecutorService;
2423
import java.util.concurrent.Executors;
2524

2625
public class SshClientCommandProcessor implements CommandProcessor, AutoCloseable {
2726

2827
final private static Logger LOG = LoggerFactory.getLogger(SshClientCommandProcessor.class);
29-
final private static int ENTER = 13;
3028

3129
private final ObjectMapper mapper;
3230

3331
private String state;
3432
private long sessionId;
33+
private OutputWriter outputWriter;
3534
private SshClientSessionListenerImpl sshClientSessionListener;
3635
private ExecutorService executorService;
3736

@@ -43,12 +42,13 @@ public SshClientCommandProcessor(SshClientSessionListenerImpl sshClientSessionLi
4342
}
4443

4544
@Override
46-
public void updateSessionId(long sessionId) {
45+
public void onSessionStart(long sessionId, OutputWriter outputWriter) {
4746
this.sessionId = sessionId;
47+
this.outputWriter = outputWriter;
4848
}
4949

5050
@Override
51-
public CommandResult processCommand(byte[] command, OutputStream stdout, OutputStream stderr) throws IOException {
51+
public CommandResult processCommand(byte[] command) throws IOException {
5252
LOG.info("processing command: {} ", new String(command, Charset.forName("UTF-8")));
5353
JsonNode jsonNode = mapper.readTree(command);
5454
String type = jsonNode.get("type").asText();
@@ -86,8 +86,7 @@ public void run() {
8686
ErrorData errorData = new ErrorData("unsupported command");
8787
response = mapper.writeValueAsBytes(errorData);
8888
}
89-
stdout.write(response);
90-
stdout.write(ENTER);
89+
outputWriter.writeMessage(response);
9190
return CommandResult.ok();
9291
}
9392

ssh-server/ssh-examples/src/main/java/itx/examples/sshd/commands/StringCommandProcessorImpl.java

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@
22

33
import itx.ssh.server.commands.CommandProcessor;
44
import itx.ssh.server.commands.CommandResult;
5+
import itx.ssh.server.commands.OutputWriter;
56
import org.slf4j.Logger;
67
import org.slf4j.LoggerFactory;
78

89
import java.io.IOException;
9-
import java.io.OutputStream;
1010
import java.nio.charset.Charset;
1111

1212
public class StringCommandProcessorImpl implements CommandProcessor, AutoCloseable {
@@ -18,18 +18,20 @@ public class StringCommandProcessorImpl implements CommandProcessor, AutoCloseab
1818

1919
private String state;
2020
private long sessionId;
21+
private OutputWriter outputWriter;
2122

2223
public StringCommandProcessorImpl() {
2324
state = "";
2425
}
2526

2627
@Override
27-
public void updateSessionId(long sessionId) {
28+
public void onSessionStart(long sessionId, OutputWriter outputWriter) {
2829
this.sessionId = sessionId;
30+
this.outputWriter = outputWriter;
2931
}
3032

3133
@Override
32-
public CommandResult processCommand(byte[] command, OutputStream stdout, OutputStream stderr) throws IOException {
34+
public CommandResult processCommand(byte[] command) throws IOException {
3335
String cmd = new String(command, Charset.forName("UTF-8"));
3436
String[] cmdElements = cmd.split(" ");
3537
if (CMD_SET.equals(cmdElements[0].trim())) {
@@ -42,11 +44,12 @@ public CommandResult processCommand(byte[] command, OutputStream stdout, OutputS
4244
return CommandResult.ok();
4345
} else if (CMD_GET.equals(cmdElements[0].trim())) {
4446
LOG.info("command: {}", cmd);
45-
stdout.write("state: ".getBytes());
46-
stdout.write(state.getBytes());
47-
stdout.write('\n');
48-
stdout.write('\r');
49-
stdout.flush();
47+
StringBuilder sb = new StringBuilder();
48+
sb.append("state: ");
49+
sb.append(state.getBytes());
50+
sb.append('\n');
51+
sb.append('\r');
52+
outputWriter.writeStdOutAndFlush(sb.toString().getBytes(Charset.forName("UTF-8")));
5053
return CommandResult.ok();
5154
} else if (CMD_EXIT.equals(cmdElements[0].trim())) {
5255
LOG.info("exit");
Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package itx.ssh.server.commands;
22

33
import java.io.IOException;
4-
import java.io.OutputStream;
54

65
/**
76
* Implementation of this interface encapsulates command processing logic on ssh-server.
@@ -10,18 +9,18 @@ public interface CommandProcessor {
109

1110
/**
1211
* Set client session ID for this command processor.
12+
* Commands are processed after the session has started.
13+
* @param outputWriter interface for writing output data
1314
* @param sessionId
1415
*/
15-
void updateSessionId(long sessionId);
16+
void onSessionStart(long sessionId, OutputWriter outputWriter);
1617

1718
/**
18-
* This method is called when new command is available.
19+
* This method is called when new command is available. It is called after session has started.
1920
* @param command command data to be processed.
20-
* @param stdout standard output stream to write processing data in.
21-
* @param stderr standard error output stream to write processing data in.
2221
* @return {@link CommandResult} for ssh-server and return code of processed command.
2322
* @throws IOException
2423
*/
25-
CommandResult processCommand(byte[] command, OutputStream stdout, OutputStream stderr) throws IOException;
24+
CommandResult processCommand(byte[] command) throws IOException;
2625

2726
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package itx.ssh.server.commands;
2+
3+
import java.io.IOException;
4+
5+
/**
6+
* Proxy for writing data into stdout and stderr output streams.
7+
*/
8+
public interface OutputWriter {
9+
10+
/**
11+
* Write byte into stdout and flush.
12+
* @param b data to write.
13+
* @throws IOException
14+
*/
15+
void writeStdOutAndFlush(byte b) throws IOException;
16+
17+
/**
18+
* Write bytes into stdout and flush.
19+
* @param bytes data to write.
20+
* @throws IOException
21+
*/
22+
void writeStdOutAndFlush(byte[] bytes) throws IOException;
23+
24+
/**
25+
* Write byte into stdout and flush.
26+
* @param b data to write.
27+
* @throws IOException
28+
*/
29+
void writeStdErrAndFlush(byte b) throws IOException;
30+
31+
/**
32+
* Write bytes into stdout and flush.
33+
* @param bytes data to write.
34+
* @throws IOException
35+
*/
36+
void writeStdErrAndFlush(byte[] bytes) throws IOException;
37+
38+
/**
39+
* Write message bytes into stdout appends character 13 (ENTER) and flush.
40+
* @param bytes message payload to write.
41+
* @throws IOException
42+
*/
43+
void writeMessage(byte[] bytes) throws IOException;
44+
45+
}

ssh-server/ssh-server/src/main/java/itx/ssh/server/commands/keymaps/DefaultKeyMap.java

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,93 +4,93 @@
44

55
public class DefaultKeyMap implements KeyMap {
66

7-
final private static int ENTER = 13;
8-
final private static int BACKSPACE = 127;
9-
final private static int SEQUENCE_PREFIX = 27;
10-
final private static int ARROW_PREFIX = 91;
11-
final private static int[] ARROW_LEFT_SEQUENCE = { SEQUENCE_PREFIX, ARROW_PREFIX, 68 };
12-
final private static int[] ARROW_RIGHT_SEQUENCE = { SEQUENCE_PREFIX, ARROW_PREFIX, 67 };
13-
final private static int[] DELETE_SEQUENCE = {SEQUENCE_PREFIX, ARROW_PREFIX, 51 };
14-
final private static int[] HOME_SEQUENCE = {SEQUENCE_PREFIX, ARROW_PREFIX, 72 };
15-
final private static int[] END_SEQUENCE = {SEQUENCE_PREFIX, ARROW_PREFIX, 70 };
7+
final private static byte ENTER = 13;
8+
final private static byte BACKSPACE = 127;
9+
final private static byte SEQUENCE_PREFIX = 27;
10+
final private static byte ARROW_PREFIX = 91;
11+
final private static byte[] ARROW_LEFT_SEQUENCE = { SEQUENCE_PREFIX, ARROW_PREFIX, 68 };
12+
final private static byte[] ARROW_RIGHT_SEQUENCE = { SEQUENCE_PREFIX, ARROW_PREFIX, 67 };
13+
final private static byte[] DELETE_SEQUENCE = {SEQUENCE_PREFIX, ARROW_PREFIX, 51 };
14+
final private static byte[] HOME_SEQUENCE = {SEQUENCE_PREFIX, ARROW_PREFIX, 72 };
15+
final private static byte[] END_SEQUENCE = {SEQUENCE_PREFIX, ARROW_PREFIX, 70 };
1616

1717
@Override
18-
public int getEnterKeyCode() {
18+
public byte getEnterKeyCode() {
1919
return ENTER;
2020
}
2121

2222
@Override
23-
public int getBackSpaceKeyCode() {
23+
public byte getBackSpaceKeyCode() {
2424
return BACKSPACE;
2525
}
2626

2727
@Override
28-
public int getSequencePrefix() {
28+
public byte getSequencePrefix() {
2929
return SEQUENCE_PREFIX;
3030
}
3131

3232
@Override
33-
public boolean isKeyLeftSequence(int... sequence) {
33+
public boolean isKeyLeftSequence(byte... sequence) {
3434
return sequence != null && sequence.length >= 3 &&
3535
sequence[0] == ARROW_LEFT_SEQUENCE[0] &&
3636
sequence[1] == ARROW_LEFT_SEQUENCE[1] &&
3737
sequence[2] == ARROW_LEFT_SEQUENCE[2];
3838
}
3939

4040
@Override
41-
public boolean isKeyRightSequence(int... sequence) {
41+
public boolean isKeyRightSequence(byte... sequence) {
4242
return sequence != null && sequence.length >= 3 &&
4343
sequence[0] == ARROW_RIGHT_SEQUENCE[0] &&
4444
sequence[1] == ARROW_RIGHT_SEQUENCE[1] &&
4545
sequence[2] == ARROW_RIGHT_SEQUENCE[2];
4646
}
4747

4848
@Override
49-
public boolean isKeyDeleteSequence(int... sequence) {
49+
public boolean isKeyDeleteSequence(byte... sequence) {
5050
return sequence != null && sequence.length >= 3 &&
5151
sequence[0] == DELETE_SEQUENCE[0] &&
5252
sequence[1] == DELETE_SEQUENCE[1] &&
5353
sequence[2] == DELETE_SEQUENCE[2];
5454
}
5555

5656
@Override
57-
public boolean isKeyHomeSequence(int... sequence) {
57+
public boolean isKeyHomeSequence(byte... sequence) {
5858
return sequence != null && sequence.length >= 3 &&
5959
sequence[0] == HOME_SEQUENCE[0] &&
6060
sequence[1] == HOME_SEQUENCE[1] &&
6161
sequence[2] == HOME_SEQUENCE[2];
6262
}
6363

6464
@Override
65-
public boolean isKeyEndSequence(int... sequence) {
65+
public boolean isKeyEndSequence(byte... sequence) {
6666
return sequence != null && sequence.length >= 3 &&
6767
sequence[0] == END_SEQUENCE[0] &&
6868
sequence[1] == END_SEQUENCE[1] &&
6969
sequence[2] == END_SEQUENCE[2];
7070
}
7171

7272
@Override
73-
public int[] getKeyLeftSequence() {
73+
public byte[] getKeyLeftSequence() {
7474
return Arrays.copyOf(ARROW_LEFT_SEQUENCE, ARROW_LEFT_SEQUENCE.length);
7575
}
7676

7777
@Override
78-
public int[] getKeyRightSequence() {
78+
public byte[] getKeyRightSequence() {
7979
return Arrays.copyOf(ARROW_RIGHT_SEQUENCE, ARROW_RIGHT_SEQUENCE.length);
8080
}
8181

8282
@Override
83-
public int[] getKeyDeleteSequence() {
83+
public byte[] getKeyDeleteSequence() {
8484
return Arrays.copyOf(DELETE_SEQUENCE, DELETE_SEQUENCE.length);
8585
}
8686

8787
@Override
88-
public int[] getKeyHomeSequence() {
88+
public byte[] getKeyHomeSequence() {
8989
return Arrays.copyOf(HOME_SEQUENCE, HOME_SEQUENCE.length);
9090
}
9191

9292
@Override
93-
public int[] getKeyEndSequence() {
93+
public byte[] getKeyEndSequence() {
9494
return Arrays.copyOf(END_SEQUENCE, END_SEQUENCE.length);
9595
}
9696

ssh-server/ssh-server/src/main/java/itx/ssh/server/commands/keymaps/KeyMap.java

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,30 @@
22

33
public interface KeyMap {
44

5-
int getEnterKeyCode();
5+
byte getEnterKeyCode();
66

7-
int getBackSpaceKeyCode();
7+
byte getBackSpaceKeyCode();
88

9-
int getSequencePrefix();
9+
byte getSequencePrefix();
1010

11-
boolean isKeyLeftSequence(int... sequence);
11+
boolean isKeyLeftSequence(byte... sequence);
1212

13-
boolean isKeyRightSequence(int... sequence);
13+
boolean isKeyRightSequence(byte... sequence);
1414

15-
boolean isKeyDeleteSequence(int... sequence);
15+
boolean isKeyDeleteSequence(byte... sequence);
1616

17-
boolean isKeyHomeSequence(int... sequence);
17+
boolean isKeyHomeSequence(byte... sequence);
1818

19-
boolean isKeyEndSequence(int... sequence);
19+
boolean isKeyEndSequence(byte... sequence);
2020

21-
int[] getKeyLeftSequence();
21+
byte[] getKeyLeftSequence();
2222

23-
int[] getKeyRightSequence();
23+
byte[] getKeyRightSequence();
2424

25-
int[] getKeyDeleteSequence();
25+
byte[] getKeyDeleteSequence();
2626

27-
int[] getKeyHomeSequence();
27+
byte[] getKeyHomeSequence();
2828

29-
int[] getKeyEndSequence();
29+
byte[] getKeyEndSequence();
3030

3131
}

ssh-server/ssh-server/src/main/java/itx/ssh/server/commands/repl/REPLCommand.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package itx.ssh.server.commands.repl;
22

33
import itx.ssh.server.commands.CommandProcessor;
4+
import itx.ssh.server.commands.OutputWriter;
45
import itx.ssh.server.commands.keymaps.KeyMap;
56
import itx.ssh.server.commands.subsystem.SshClientSessionCounter;
7+
import itx.ssh.server.utils.OutputWriterImpl;
68
import org.apache.sshd.server.Environment;
79
import org.apache.sshd.server.ExitCallback;
810
import org.apache.sshd.server.command.Command;
@@ -64,10 +66,11 @@ public void setExitCallback(ExitCallback callback) {
6466
@Override
6567
public void start(Environment env) throws IOException {
6668
long sessionId = sshClientSessionCounter.getNewSessionId();
67-
commandProcessor.updateSessionId(sessionId);
69+
OutputWriterImpl outputWriter = new OutputWriterImpl(stdout, stderr);
70+
commandProcessor.onSessionStart(sessionId, outputWriter);
6871
LOG.info("start REPL command processor with sessionId: {}", sessionId);
6972
REPLCommandProcessor replCommandProcessor = new REPLCommandProcessor(prompt, keyMap, commandProcessor,
70-
stdin, stdout, stderr, exitCallback);
73+
stdin, outputWriter, exitCallback);
7174
executorService.submit(replCommandProcessor);
7275
}
7376

0 commit comments

Comments
 (0)