Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ default Map<String, Object> executeSync(String commandLine, Object authSubject,

Map<String, Object> interruptJob(String sessionId);

Map<String, Object> createSession();
Map<String, Object> createSession(boolean quiet);

Map<String, Object> closeSession(String sessionId);

Expand All @@ -61,4 +61,3 @@ default Map<String, Object> executeSync(String commandLine, Object authSubject,
*/
void setSessionUserId(String sessionId, String userId);
}

Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public void updateAccessTime() {
}

public CommandSessionBinding createCommandSession(String mcpSessionId) {
Map<String, Object> result = commandExecutor.createSession();
Map<String, Object> result = commandExecutor.createSession(true);

CommandSessionBinding binding = new CommandSessionBinding(
mcpSessionId,
Expand Down Expand Up @@ -177,7 +177,7 @@ public CommandSessionBinding createIsolatedTaskSession(String taskId) {
.build();
}

Map<String, Object> result = commandExecutor.createSession();
Map<String, Object> result = commandExecutor.createSession(true);

CommandSessionBinding binding = new CommandSessionBinding(
"task-" + taskId, // 使用 task ID 作为 MCP session ID
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public Map<String, Object> interruptJob(String sessionId) {
}

@Override
public Map<String, Object> createSession() {
public Map<String, Object> createSession(boolean quiet) {
Map<String, Object> result = new HashMap<String, Object>();
result.put("sessionId", "created-session");
result.put("consumerId", "created-consumer");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.taobao.arthas.mcp.server.session;

import com.taobao.arthas.mcp.server.CommandExecutor;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

class ArthasCommandSessionManagerQuietTest {

@Test
void commandSessionsShouldBeCreatedQuietly() {
RecordingCommandExecutor executor = new RecordingCommandExecutor();
ArthasCommandSessionManager sessionManager = new ArthasCommandSessionManager(executor);

sessionManager.createCommandSession("mcp-session");
sessionManager.createIsolatedTaskSession("task-1");

assertThat(executor.quietFlags).containsExactly(Boolean.TRUE, Boolean.TRUE);
}

private static final class RecordingCommandExecutor implements CommandExecutor {
private final List<Boolean> quietFlags = new ArrayList<Boolean>();

@Override
public Map<String, Object> executeSync(String commandLine, long timeout, String sessionId,
Object authSubject, String userId) {
return new HashMap<String, Object>();
}

@Override
public Map<String, Object> executeAsync(String commandLine, String sessionId) {
return new HashMap<String, Object>();
}

@Override
public Map<String, Object> pullResults(String sessionId, String consumerId) {
return new HashMap<String, Object>();
}

@Override
public Map<String, Object> interruptJob(String sessionId) {
return new HashMap<String, Object>();
}

@Override
public Map<String, Object> createSession(boolean quiet) {
quietFlags.add(quiet);
Map<String, Object> result = new HashMap<String, Object>();
result.put("sessionId", "session-" + quietFlags.size());
result.put("consumerId", "consumer-" + quietFlags.size());
return result;
}

@Override
public Map<String, Object> closeSession(String sessionId) {
return new HashMap<String, Object>();
}

@Override
public void setSessionAuth(String sessionId, Object authSubject) {
}

@Override
public void setSessionUserId(String sessionId, String userId) {
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ public Map<String, Object> interruptJob(String sessionId) {
}

@Override
public Map<String, Object> createSession() {
public Map<String, Object> createSession(boolean quiet) {
Map<String, Object> result = new HashMap<String, Object>();
result.put("sessionId", "isolated-session");
result.put("consumerId", "isolated-consumer");
Expand Down
15 changes: 14 additions & 1 deletion client/src/main/java/com/taobao/arthas/client/TelnetConsole.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public class TelnetConsole {

private Integer width = null;
private Integer height = null;
private boolean quiet = false;

@Argument(argName = "target-ip", index = 0, required = false)
@Description("Target ip")
Expand Down Expand Up @@ -133,6 +134,12 @@ public void setheight(int height) {
this.height = height;
}

@Option(longName = "quiet", flag = true)
@Description("Suppress connection welcome output")
public void setQuiet(boolean quiet) {
this.quiet = quiet;
}

public TelnetConsole() {
}

Expand Down Expand Up @@ -273,7 +280,9 @@ public static int process(String[] args, ActionListener eotEventCallback) throws
}
}

final TelnetClient telnet = new TelnetClient();
final TelnetClient telnet = telnetConsole.isQuiet()
? new TelnetClient("arthas-agent")
: new TelnetClient();
Comment on lines +283 to +285

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle prompts at column 0 for quiet batch sessions

When --quiet is combined with -c/-f, this terminal type suppresses the welcome banner, so the first server output can be the prompt itself at the start of the stream. batchModeRun only releases the command loop when line.indexOf(PROMPT) > 0, so a prompt at index 0 is ignored and the client hangs indefinitely with the default timeout before sending the first command. Either avoid quiet mode for batch runs or update the prompt detection to accept index 0.

Useful? React with 👍 / 👎.

telnet.setConnectTimeout(DEFAULT_CONNECTION_TIMEOUT);

// send init terminal size
Expand Down Expand Up @@ -446,6 +455,10 @@ public Integer getheight() {
return height;
}

public boolean isQuiet() {
return quiet;
}

public boolean isHelp() {
return help;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,17 +279,34 @@ public Map<String, Object> interruptJob(String sessionId) {
}

@Override
public Map<String, Object> createSession() {
public Map<String, Object> createSession(boolean quiet) {
Session session = sessionManager.createSession();
if (session == null) {
return createErrorResult(null, "create api session failed");
}
if (quiet) {
session.put(Session.QUIET, Boolean.TRUE);
}

SharingResultDistributorImpl resultDistributor = new SharingResultDistributorImpl(session);
ResultConsumer resultConsumer = new ResultConsumerImpl();
resultDistributor.addConsumer(resultConsumer);
session.setResultDistributor(resultDistributor);

if (!quiet) {
appendWelcomeResults(resultDistributor);
}

updateSessionInputStatus(session, InputStatus.ALLOW_INPUT);

Map<String, Object> result = new TreeMap<>();
result.put("success", true);
result.put("sessionId", session.getSessionId());
result.put("consumerId", resultConsumer.getConsumerId());
return result;
}

private void appendWelcomeResults(ResultDistributor resultDistributor) {
resultDistributor.appendResult(new MessageModel("Welcome to arthas!"));

WelcomeModel welcomeModel = new WelcomeModel();
Expand All @@ -300,14 +317,6 @@ public Map<String, Object> createSession() {
welcomeModel.setPid(PidUtils.currentPid());
welcomeModel.setTime(DateUtils.getCurrentDateTime());
resultDistributor.appendResult(welcomeModel);

updateSessionInputStatus(session, InputStatus.ALLOW_INPUT);

Map<String, Object> result = new TreeMap<>();
result.put("success", true);
result.put("sessionId", session.getSessionId());
result.put("consumerId", resultConsumer.getConsumerId());
return result;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
*/
public class ShellImpl implements Shell {
private static final Logger logger = LoggerFactory.getLogger(ShellImpl.class);
private SecurityAuthenticator securityAuthenticator = ArthasBootstrap.getInstance().getSecurityAuthenticator();
private static final String ARTHAS_AGENT_TERMINAL_TYPE = "arthas-agent";

private JobControllerImpl jobController;
final String id;
Expand All @@ -78,6 +78,7 @@ public ShellImpl(ShellServer server, Term term, InternalCommandManager commandMa
Principal principal = AuthUtils.localPrincipal(handlerContext);
if (principal != null) {
try {
SecurityAuthenticator securityAuthenticator = ArthasBootstrap.getInstance().getSecurityAuthenticator();
Subject subject = securityAuthenticator.login(principal);
if (subject != null) {
session.put(ArthasConstants.SUBJECT_KEY, subject);
Expand All @@ -98,6 +99,9 @@ public ShellImpl(ShellServer server, Term term, InternalCommandManager commandMa
}
}
}
if (term != null && ARTHAS_AGENT_TERMINAL_TYPE.equalsIgnoreCase(term.type())) {
session.put(Session.QUIET, Boolean.TRUE);
}
session.put(Session.COMMAND_MANAGER, commandManager);
session.put(Session.INSTRUMENTATION, instrumentation);
session.put(Session.PID, pid);
Expand Down Expand Up @@ -168,12 +172,16 @@ public ShellImpl init() {
term.suspendHandler(new SuspendHandler(this));
term.closeHandler(new CloseHandler(this));

if (welcome != null && welcome.length() > 0) {
if (!isQuietSession() && welcome != null && welcome.length() > 0) {
term.write(welcome + "\n");
}
return this;
}

private boolean isQuietSession() {
return Boolean.TRUE.equals(session.get(Session.QUIET));
}

public String statusLine(Job job, ExecStatus status) {
StringBuilder sb = new StringBuilder("[").append(job.id()).append("]");
if (this.session().equals(job.getSession())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ public interface Session {
String ID = "id";
String SERVER = "server";
String USER_ID = "userId";
/**
* 会话静默模式,不输出连接欢迎信息。
*/
String QUIET = "arthas-session-quiet";
/**
* The tty this session related to.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.concurrent.TimeUnit;

import com.taobao.arthas.common.ArthasConstants;
import com.taobao.arthas.core.shell.session.Session;
import com.taobao.arthas.core.shell.term.impl.http.session.HttpSession;
import com.taobao.arthas.core.shell.term.impl.http.session.HttpSessionManager;

Expand All @@ -23,9 +24,15 @@
*/
public class ExtHttpTtyConnection extends HttpTtyConnection {
private ChannelHandlerContext context;
private final boolean quiet;

public ExtHttpTtyConnection(ChannelHandlerContext context) {
this(context, false);
}

public ExtHttpTtyConnection(ChannelHandlerContext context, boolean quiet) {
this.context = context;
this.quiet = quiet;
}

@Override
Expand Down Expand Up @@ -59,10 +66,13 @@ public void close() {
}

public Map<String, Object> extSessions() {
Map<String, Object> result = new HashMap<String, Object>();
if (quiet) {
result.put(Session.QUIET, Boolean.TRUE);
}
if (context != null) {
HttpSession httpSession = HttpSessionManager.getHttpSessionFromContext(context);
if (httpSession != null) {
Map<String, Object> result = new HashMap<String, Object>();
Object subject = httpSession.getAttribute(ArthasConstants.SUBJECT_KEY);
if (subject != null) {
result.put(ArthasConstants.SUBJECT_KEY, subject);
Expand All @@ -72,11 +82,11 @@ public Map<String, Object> extSessions() {
if (userId != null) {
result.put(ArthasConstants.USER_ID_KEY, userId);
}
if (!result.isEmpty()) {
return result;
}
}
}
if (!result.isEmpty()) {
return result;
}
return Collections.emptyMap();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.handler.codec.http.QueryStringDecoder;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
Expand All @@ -27,6 +28,7 @@
import io.termd.core.http.HttpTtyConnection;
import io.termd.core.tty.TtyConnection;

import java.util.List;

/**
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
Expand All @@ -52,10 +54,17 @@ public void channelActive(ChannelHandlerContext ctx) throws Exception {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt == WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE) {
ctx.pipeline().remove(HttpRequestHandler.class);
group.add(ctx.channel());
conn = new ExtHttpTtyConnection(context);
handler.accept(conn);
// Netty 会先发旧事件,再发带 requestUri 的 HandshakeComplete;这里延迟兜底,优先读取 query。
ctx.executor().execute(new Runnable() {
@Override
public void run() {
handleHandshakeComplete(ctx, null);
}
});
} else if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
WebSocketServerProtocolHandler.HandshakeComplete handshakeComplete =
(WebSocketServerProtocolHandler.HandshakeComplete) evt;
handleHandshakeComplete(ctx, handshakeComplete.requestUri());
} else if (evt instanceof IdleStateEvent) {
ctx.writeAndFlush(new PingWebSocketFrame());
} else {
Expand All @@ -80,4 +89,30 @@ public void channelInactive(ChannelHandlerContext ctx) throws Exception {
public void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
conn.writeToDecoder(msg.text());
}

private void handleHandshakeComplete(ChannelHandlerContext ctx, String requestUri) {
if (conn != null) {
return;
}
ctx.pipeline().remove(HttpRequestHandler.class);
group.add(ctx.channel());
conn = new ExtHttpTtyConnection(context, isQuietRequest(requestUri));
handler.accept(conn);
}

static boolean isQuietRequest(String requestUri) {
if (requestUri == null) {
return false;
}
List<String> values = new QueryStringDecoder(requestUri).parameters().get("quiet");
if (values == null) {
return false;
}
for (String value : values) {
if ("true".equalsIgnoreCase(value)) {
return true;
}
}
return false;
}
}
Loading
Loading