mirror of
https://github.com/alibaba/arthas.git
synced 2024-04-21 10:21:39 +00:00
Add auth command, support authentication (#1724)
This commit is contained in:
@@ -8,6 +8,10 @@ arthas.sessionTimeout=1800
|
||||
|
||||
arthas.enhanceLoaders=java.lang.ClassLoader
|
||||
|
||||
# https://arthas.aliyun.com/doc/en/auth
|
||||
# arthas.username=arthas
|
||||
# arthas.password=arthas
|
||||
|
||||
#arthas.appName=demoapp
|
||||
#arthas.tunnelServer=ws://127.0.0.1:7777/ws
|
||||
#arthas.agentId=mmmmmmyiddddd
|
||||
|
||||
@@ -38,6 +38,9 @@ public class Arthas {
|
||||
Option sessionTimeout = new TypedOption<Integer>().setType(Integer.class)
|
||||
.setShortName("session-timeout");
|
||||
|
||||
Option username = new TypedOption<String>().setType(String.class).setShortName("username");
|
||||
Option password = new TypedOption<String>().setType(String.class).setShortName("password");
|
||||
|
||||
Option tunnelServer = new TypedOption<String>().setType(String.class).setShortName("tunnel-server");
|
||||
Option agentId = new TypedOption<String>().setType(String.class).setShortName("agent-id");
|
||||
Option appName = new TypedOption<String>().setType(String.class).setShortName(ArthasConstants.APP_NAME);
|
||||
@@ -45,7 +48,9 @@ public class Arthas {
|
||||
Option statUrl = new TypedOption<String>().setType(String.class).setShortName("stat-url");
|
||||
|
||||
CLI cli = CLIs.create("arthas").addOption(pid).addOption(core).addOption(agent).addOption(target)
|
||||
.addOption(telnetPort).addOption(httpPort).addOption(sessionTimeout).addOption(tunnelServer).addOption(agentId).addOption(appName).addOption(statUrl);
|
||||
.addOption(telnetPort).addOption(httpPort).addOption(sessionTimeout)
|
||||
.addOption(username).addOption(password)
|
||||
.addOption(tunnelServer).addOption(agentId).addOption(appName).addOption(statUrl);
|
||||
CommandLine commandLine = cli.parse(Arrays.asList(args));
|
||||
|
||||
Configure configure = new Configure();
|
||||
@@ -67,6 +72,9 @@ public class Arthas {
|
||||
configure.setHttpPort((Integer) commandLine.getOptionValue("http-port"));
|
||||
}
|
||||
|
||||
configure.setUsername((String) commandLine.getOptionValue("username"));
|
||||
configure.setPassword((String) commandLine.getOptionValue("password"));
|
||||
|
||||
configure.setTunnelServer((String) commandLine.getOptionValue("tunnel-server"));
|
||||
configure.setAgentId((String) commandLine.getOptionValue("agent-id"));
|
||||
configure.setStatUrl((String) commandLine.getOptionValue("stat-url"));
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.taobao.arthas.core.command.basic1000.HelpCommand;
|
||||
import com.taobao.arthas.core.command.basic1000.HistoryCommand;
|
||||
import com.taobao.arthas.core.command.basic1000.KeymapCommand;
|
||||
import com.taobao.arthas.core.command.basic1000.OptionsCommand;
|
||||
import com.taobao.arthas.core.command.basic1000.AuthCommand;
|
||||
import com.taobao.arthas.core.command.basic1000.PwdCommand;
|
||||
import com.taobao.arthas.core.command.basic1000.ResetCommand;
|
||||
import com.taobao.arthas.core.command.basic1000.SessionCommand;
|
||||
@@ -69,6 +70,7 @@ public class BuiltinCommandPack implements CommandResolver {
|
||||
|
||||
private static void initCommands() {
|
||||
commands.add(Command.create(HelpCommand.class));
|
||||
commands.add(Command.create(AuthCommand.class));
|
||||
commands.add(Command.create(KeymapCommand.class));
|
||||
commands.add(Command.create(SearchClassCommand.class));
|
||||
commands.add(Command.create(SearchMethodCommand.class));
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package com.taobao.arthas.core.command.basic1000;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
import javax.security.auth.login.LoginException;
|
||||
|
||||
import com.alibaba.arthas.deps.org.slf4j.Logger;
|
||||
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
|
||||
import com.taobao.arthas.common.ArthasConstants;
|
||||
import com.taobao.arthas.core.command.Constants;
|
||||
import com.taobao.arthas.core.security.BasicPrincipal;
|
||||
import com.taobao.arthas.core.security.SecurityAuthenticator;
|
||||
import com.taobao.arthas.core.server.ArthasBootstrap;
|
||||
import com.taobao.arthas.core.shell.cli.Completion;
|
||||
import com.taobao.arthas.core.shell.cli.CompletionUtils;
|
||||
import com.taobao.arthas.core.shell.command.AnnotatedCommand;
|
||||
import com.taobao.arthas.core.shell.command.CommandProcess;
|
||||
import com.taobao.arthas.core.shell.session.Session;
|
||||
import com.taobao.middleware.cli.annotations.Argument;
|
||||
import com.taobao.middleware.cli.annotations.DefaultValue;
|
||||
import com.taobao.middleware.cli.annotations.Description;
|
||||
import com.taobao.middleware.cli.annotations.Name;
|
||||
import com.taobao.middleware.cli.annotations.Option;
|
||||
import com.taobao.middleware.cli.annotations.Summary;
|
||||
|
||||
/**
|
||||
* TODO 支持更多的鉴权方式。目前只支持 username/password的方式
|
||||
*
|
||||
* @author hengyunabc 2021-03-03
|
||||
*
|
||||
*/
|
||||
// @formatter:off
|
||||
@Name(ArthasConstants.AUTH)
|
||||
@Summary("Authenticates the current session")
|
||||
@Description(Constants.EXAMPLE +
|
||||
" auth" +
|
||||
" auth <password>\n" +
|
||||
" auth --username <username> <password>\n"
|
||||
+ Constants.WIKI + Constants.WIKI_HOME + ArthasConstants.AUTH)
|
||||
//@formatter:on
|
||||
public class AuthCommand extends AnnotatedCommand {
|
||||
private static final Logger logger = LoggerFactory.getLogger(AuthCommand.class);
|
||||
|
||||
private String username;
|
||||
private String password;
|
||||
private SecurityAuthenticator authenticator = ArthasBootstrap.getInstance().getSecurityAuthenticator();
|
||||
|
||||
@Argument(argName = "password", index = 0, required = false)
|
||||
@Description("password")
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@Option(shortName = "n", longName = "username")
|
||||
@Description("username, default value 'arthas'")
|
||||
@DefaultValue(ArthasConstants.DEFAULT_USERNAME)
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(CommandProcess process) {
|
||||
int status = 0;
|
||||
String message = "";
|
||||
try {
|
||||
Session session = process.session();
|
||||
if (username == null) {
|
||||
status = 1;
|
||||
message = "username can not be empty!";
|
||||
return;
|
||||
}
|
||||
if (password == null) { // 没有传入passowrd参数时,打印当前结果
|
||||
boolean authenticated = session.get(ArthasConstants.SUBJECT_KEY) != null;
|
||||
boolean needLogin = this.authenticator.needLogin();
|
||||
|
||||
message = "Authentication result: " + authenticated + ", Need authentication: " + needLogin;
|
||||
if (needLogin && !authenticated) {
|
||||
status = 1;
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
// 尝试进行鉴权
|
||||
BasicPrincipal principal = new BasicPrincipal(username, password);
|
||||
try {
|
||||
Subject subject = authenticator.login(principal);
|
||||
if (subject != null) {
|
||||
// 把subject 保存到 session里,后续其它命令则可以正常执行
|
||||
session.put(ArthasConstants.SUBJECT_KEY, subject);
|
||||
message = "Authentication result: " + true + ", username: " + username;
|
||||
} else {
|
||||
status = 1;
|
||||
message = "Authentication result: " + false + ", username: " + username;
|
||||
}
|
||||
} catch (LoginException e) {
|
||||
logger.error("Authentication error, username: {}", username, e);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
process.end(status, message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void complete(Completion completion) {
|
||||
if (!CompletionUtils.completeFilePath(completion)) {
|
||||
super.complete(completion);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,6 +28,9 @@ public class Configure {
|
||||
private String tunnelServer;
|
||||
private String agentId;
|
||||
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* @see com.taobao.arthas.common.ArthasConstants#ARTHAS_OUTPUT
|
||||
*/
|
||||
@@ -161,6 +164,22 @@ public class Configure {
|
||||
this.outputPath = outputPath;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
/**
|
||||
* 序列化成字符串
|
||||
*
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.taobao.arthas.core.security;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
/**
|
||||
* Basic {@link Principal}.
|
||||
*
|
||||
* @author hengyunabc 2021-03-04
|
||||
*/
|
||||
public final class BasicPrincipal implements Principal {
|
||||
|
||||
private final String username;
|
||||
private final String password;
|
||||
|
||||
public BasicPrincipal(String username, String password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((password == null) ? 0 : password.hashCode());
|
||||
result = prime * result + ((username == null) ? 0 : username.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
BasicPrincipal other = (BasicPrincipal) obj;
|
||||
if (password == null) {
|
||||
if (other.password != null)
|
||||
return false;
|
||||
} else if (!password.equals(other.password))
|
||||
return false;
|
||||
if (username == null) {
|
||||
if (other.username != null)
|
||||
return false;
|
||||
} else if (!username.equals(other.username))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
// do not display the password
|
||||
return "BasicPrincipal[" + username + "]";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.taobao.arthas.core.security;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
import javax.security.auth.login.LoginException;
|
||||
|
||||
/**
|
||||
* A {@link SecurityAuthenticator} allows to plugin custom authenticators, such
|
||||
* as JAAS based or custom implementations.
|
||||
*/
|
||||
public interface SecurityAuthenticator {
|
||||
|
||||
|
||||
boolean needLogin();
|
||||
|
||||
/**
|
||||
* Sets the name of the realm to use.
|
||||
*/
|
||||
void setName(String name);
|
||||
|
||||
/**
|
||||
* Gets the name of the realm.
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Sets the role class names (separated by comma)
|
||||
* <p/>
|
||||
* By default if no explicit role class names has been configured, then this
|
||||
* implementation will assume the {@link Subject}
|
||||
* {@link java.security.Principal}s is a role if the classname contains the word
|
||||
* <tt>role</tt> (lower cased).
|
||||
*
|
||||
* @param names a list of FQN class names for role
|
||||
* {@link java.security.Principal} implementations.
|
||||
*/
|
||||
void setRoleClassNames(String names);
|
||||
|
||||
/**
|
||||
* Attempts to login the {@link java.security.Principal} on this realm.
|
||||
* <p/>
|
||||
* The login is a success if no Exception is thrown, and a {@link Subject} is
|
||||
* returned.
|
||||
*
|
||||
* @param principal the principal
|
||||
* @return the subject for the logged in principal, must <b>not</b> be
|
||||
* <tt>null</tt>
|
||||
* @throws LoginException is thrown if error logging in the
|
||||
* {@link java.security.Principal}
|
||||
*/
|
||||
Subject login(Principal principal) throws LoginException;
|
||||
|
||||
/**
|
||||
* Attempt to logout the subject.
|
||||
*
|
||||
* @param subject subject to logout
|
||||
* @throws LoginException is thrown if error logging out subject
|
||||
*/
|
||||
void logout(Subject subject) throws LoginException;
|
||||
|
||||
/**
|
||||
* Gets the user roles from the given {@link Subject}
|
||||
*
|
||||
* @param subject the subject
|
||||
* @return <tt>null</tt> if no roles, otherwise a String with roles separated by
|
||||
* comma.
|
||||
*/
|
||||
String getUserRoles(Subject subject);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.taobao.arthas.core.security;
|
||||
|
||||
import java.security.Principal;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
import javax.security.auth.login.LoginException;
|
||||
|
||||
import com.alibaba.arthas.deps.org.slf4j.Logger;
|
||||
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
|
||||
import com.taobao.arthas.common.ArthasConstants;
|
||||
import com.taobao.arthas.core.util.StringUtils;
|
||||
|
||||
/**
|
||||
* TODO 支持不同角色不同权限,command按角色分类?
|
||||
*
|
||||
* @author hengyunabc 2021-03-03
|
||||
*
|
||||
*/
|
||||
public class SecurityAuthenticatorImpl implements SecurityAuthenticator {
|
||||
private static final Logger logger = LoggerFactory.getLogger(SecurityAuthenticatorImpl.class);
|
||||
private String username;
|
||||
private String password;
|
||||
private Subject subject;
|
||||
|
||||
public SecurityAuthenticatorImpl(String username, String password) {
|
||||
if (username != null && password == null) {
|
||||
password = StringUtils.randomString(32);
|
||||
logger.info("\nUsing generated security password: {}\n", password);
|
||||
}
|
||||
if (username == null && password != null) {
|
||||
username = ArthasConstants.DEFAULT_USERNAME;
|
||||
}
|
||||
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
|
||||
subject = new Subject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(String name) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRoleClassNames(String names) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subject login(Principal principal) throws LoginException {
|
||||
if (principal == null) {
|
||||
return null;
|
||||
}
|
||||
if (principal instanceof BasicPrincipal) {
|
||||
BasicPrincipal basicPrincipal = (BasicPrincipal) principal;
|
||||
if (basicPrincipal.getName().equals(username) && basicPrincipal.getPassword().equals(this.password)) {
|
||||
return subject;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logout(Subject subject) throws LoginException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUserRoles(Subject subject) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needLogin() {
|
||||
return username != null && password != null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -50,6 +50,8 @@ import com.taobao.arthas.core.env.ArthasEnvironment;
|
||||
import com.taobao.arthas.core.env.MapPropertySource;
|
||||
import com.taobao.arthas.core.env.PropertiesPropertySource;
|
||||
import com.taobao.arthas.core.env.PropertySource;
|
||||
import com.taobao.arthas.core.security.SecurityAuthenticator;
|
||||
import com.taobao.arthas.core.security.SecurityAuthenticatorImpl;
|
||||
import com.taobao.arthas.core.server.instrument.ClassLoader_Instrument;
|
||||
import com.taobao.arthas.core.shell.ShellServer;
|
||||
import com.taobao.arthas.core.shell.ShellServerOptions;
|
||||
@@ -62,6 +64,7 @@ import com.taobao.arthas.core.shell.session.SessionManager;
|
||||
import com.taobao.arthas.core.shell.session.impl.SessionManagerImpl;
|
||||
import com.taobao.arthas.core.shell.term.impl.HttpTermServer;
|
||||
import com.taobao.arthas.core.shell.term.impl.http.api.HttpApiHandler;
|
||||
import com.taobao.arthas.core.shell.term.impl.http.session.HttpSessionManager;
|
||||
import com.taobao.arthas.core.shell.term.impl.httptelnet.HttpTelnetTermServer;
|
||||
import com.taobao.arthas.core.util.ArthasBanner;
|
||||
import com.taobao.arthas.core.util.FileUtils;
|
||||
@@ -119,6 +122,9 @@ public class ArthasBootstrap {
|
||||
|
||||
private HttpApiHandler httpApiHandler;
|
||||
|
||||
private HttpSessionManager httpSessionManager;
|
||||
private SecurityAuthenticator securityAuthenticator;
|
||||
|
||||
private ArthasBootstrap(Instrumentation instrumentation, Map<String, String> args) throws Throwable {
|
||||
this.instrumentation = instrumentation;
|
||||
|
||||
@@ -320,7 +326,7 @@ public class ArthasBootstrap {
|
||||
overrideAll = Boolean.parseBoolean(properties.getProperty(CONFIG_OVERRIDE_ALL, "false"));
|
||||
}
|
||||
|
||||
PropertySource propertySource = new PropertiesPropertySource(location, properties);
|
||||
PropertySource<?> propertySource = new PropertiesPropertySource(location, properties);
|
||||
if (overrideAll) {
|
||||
arthasEnvironment.addFirst(propertySource);
|
||||
} else {
|
||||
@@ -383,6 +389,9 @@ public class ArthasBootstrap {
|
||||
options.setSessionTimeout(configure.getSessionTimeout() * 1000);
|
||||
}
|
||||
|
||||
this.httpSessionManager = new HttpSessionManager();
|
||||
this.securityAuthenticator = new SecurityAuthenticatorImpl(configure.getUsername(), configure.getPassword());
|
||||
|
||||
shellServer = new ShellServerImpl(options);
|
||||
BuiltinCommandPack builtinCommands = new BuiltinCommandPack();
|
||||
List<CommandResolver> resolvers = new ArrayList<CommandResolver>();
|
||||
@@ -394,18 +403,18 @@ public class ArthasBootstrap {
|
||||
// TODO: discover user provided command resolver
|
||||
if (configure.getTelnetPort() != null && configure.getTelnetPort() > 0) {
|
||||
shellServer.registerTermServer(new HttpTelnetTermServer(configure.getIp(), configure.getTelnetPort(),
|
||||
options.getConnectionTimeout(), workerGroup));
|
||||
options.getConnectionTimeout(), workerGroup, httpSessionManager));
|
||||
} else {
|
||||
logger().info("telnet port is {}, skip bind telnet server.", configure.getTelnetPort());
|
||||
}
|
||||
if (configure.getHttpPort() != null && configure.getHttpPort() > 0) {
|
||||
shellServer.registerTermServer(new HttpTermServer(configure.getIp(), configure.getHttpPort(),
|
||||
options.getConnectionTimeout(), workerGroup));
|
||||
options.getConnectionTimeout(), workerGroup, httpSessionManager));
|
||||
} else {
|
||||
// listen local address in VM communication
|
||||
if (configure.getTunnelServer() != null) {
|
||||
shellServer.registerTermServer(new HttpTermServer(configure.getIp(), configure.getHttpPort(),
|
||||
options.getConnectionTimeout(), workerGroup));
|
||||
options.getConnectionTimeout(), workerGroup, httpSessionManager));
|
||||
}
|
||||
logger().info("http port is {}, skip bind http server.", configure.getHttpPort());
|
||||
}
|
||||
@@ -480,6 +489,9 @@ public class ArthasBootstrap {
|
||||
sessionManager.close();
|
||||
sessionManager = null;
|
||||
}
|
||||
if (this.httpSessionManager != null) {
|
||||
httpSessionManager.stop();
|
||||
}
|
||||
if (timer != null) {
|
||||
timer.cancel();
|
||||
}
|
||||
@@ -633,4 +645,8 @@ public class ArthasBootstrap {
|
||||
return outputPath;
|
||||
}
|
||||
|
||||
public SecurityAuthenticator getSecurityAuthenticator() {
|
||||
return securityAuthenticator;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,13 +18,18 @@ import com.taobao.arthas.core.shell.system.impl.InternalCommandManager;
|
||||
import com.taobao.arthas.core.shell.system.impl.JobControllerImpl;
|
||||
import com.taobao.arthas.core.shell.term.Term;
|
||||
import com.taobao.arthas.core.shell.term.impl.TermImpl;
|
||||
import com.taobao.arthas.core.shell.term.impl.http.ExtHttpTtyConnection;
|
||||
import com.taobao.arthas.core.util.Constants;
|
||||
import com.taobao.arthas.core.util.FileUtils;
|
||||
|
||||
import io.termd.core.tty.TtyConnection;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -49,6 +54,18 @@ public class ShellImpl implements Shell {
|
||||
|
||||
public ShellImpl(ShellServer server, Term term, InternalCommandManager commandManager,
|
||||
Instrumentation instrumentation, long pid, JobControllerImpl jobController) {
|
||||
if (term instanceof TermImpl) {
|
||||
TermImpl termImpl = (TermImpl) term;
|
||||
TtyConnection conn = termImpl.getConn();
|
||||
if (conn instanceof ExtHttpTtyConnection) {
|
||||
// 传递http cookie 里的鉴权信息到新建立的session中
|
||||
ExtHttpTtyConnection extConn = (ExtHttpTtyConnection) conn;
|
||||
Map<String, Object> extSessions = extConn.extSessions();
|
||||
for (Entry<String, Object> entry : extSessions.entrySet()) {
|
||||
session.put(entry.getKey(), entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
session.put(Session.COMMAND_MANAGER, commandManager);
|
||||
session.put(Session.INSTRUMENTATION, instrumentation);
|
||||
session.put(Session.PID, pid);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package com.taobao.arthas.core.shell.system.impl;
|
||||
|
||||
import com.taobao.arthas.common.ArthasConstants;
|
||||
import com.taobao.arthas.core.GlobalOptions;
|
||||
import com.taobao.arthas.core.distribution.ResultDistributor;
|
||||
import com.taobao.arthas.core.server.ArthasBootstrap;
|
||||
import com.taobao.arthas.core.shell.cli.CliToken;
|
||||
import com.taobao.arthas.core.shell.command.Command;
|
||||
import com.taobao.arthas.core.shell.command.internal.RedirectHandler;
|
||||
@@ -60,15 +62,30 @@ public class JobControllerImpl implements JobController {
|
||||
return jobs.remove(id) != null;
|
||||
}
|
||||
|
||||
private void checkPermission(Session session, CliToken token) {
|
||||
if (ArthasBootstrap.getInstance().getSecurityAuthenticator().needLogin()) {
|
||||
// 检查session是否有 Subject
|
||||
Object subject = session.get(ArthasConstants.SUBJECT_KEY);
|
||||
if (subject == null) {
|
||||
if (token != null && token.isText() && token.value().trim().equals(ArthasConstants.AUTH)) {
|
||||
// 执行的是auth 命令
|
||||
return;
|
||||
}
|
||||
throw new IllegalArgumentException("Error! command not permitted, try to use 'auth' command to authenticates.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Job createJob(InternalCommandManager commandManager, List<CliToken> tokens, Session session, JobListener jobHandler, Term term, ResultDistributor resultDistributor) {
|
||||
checkPermission(session, tokens.get(0));
|
||||
int jobId = idGenerator.incrementAndGet();
|
||||
StringBuilder line = new StringBuilder();
|
||||
for (CliToken arg : tokens) {
|
||||
line.append(arg.raw());
|
||||
}
|
||||
boolean runInBackground = runInBackground(tokens);
|
||||
Process process = createProcess(tokens, commandManager, jobId, term, resultDistributor);
|
||||
Process process = createProcess(session, tokens, commandManager, jobId, term, resultDistributor);
|
||||
process.setJobId(jobId);
|
||||
JobImpl job = new JobImpl(jobId, this, process, line.toString(), runInBackground, session, jobHandler);
|
||||
jobs.put(jobId, job);
|
||||
@@ -126,12 +143,14 @@ public class JobControllerImpl implements JobController {
|
||||
* @param resultDistributor
|
||||
* @return the created process
|
||||
*/
|
||||
private Process createProcess(List<CliToken> line, InternalCommandManager commandManager, int jobId, Term term, ResultDistributor resultDistributor) {
|
||||
private Process createProcess(Session session, List<CliToken> line, InternalCommandManager commandManager, int jobId, Term term, ResultDistributor resultDistributor) {
|
||||
try {
|
||||
ListIterator<CliToken> tokens = line.listIterator();
|
||||
while (tokens.hasNext()) {
|
||||
CliToken token = tokens.next();
|
||||
if (token.isText()) {
|
||||
// check before create process
|
||||
checkPermission(session, token);
|
||||
Command command = commandManager.getCommand(token.value());
|
||||
if (command != null) {
|
||||
return createCommandProcess(command, tokens, jobId, term, resultDistributor);
|
||||
|
||||
@@ -7,6 +7,8 @@ import com.taobao.arthas.core.shell.handlers.Handler;
|
||||
import com.taobao.arthas.core.shell.term.Term;
|
||||
import com.taobao.arthas.core.shell.term.TermServer;
|
||||
import com.taobao.arthas.core.shell.term.impl.http.NettyWebsocketTtyBootstrap;
|
||||
import com.taobao.arthas.core.shell.term.impl.http.session.HttpSessionManager;
|
||||
|
||||
import io.netty.util.concurrent.EventExecutorGroup;
|
||||
import io.termd.core.function.Consumer;
|
||||
import io.termd.core.tty.TtyConnection;
|
||||
@@ -26,12 +28,14 @@ public class HttpTermServer extends TermServer {
|
||||
private int port;
|
||||
private long connectionTimeout;
|
||||
private EventExecutorGroup workerGroup;
|
||||
private HttpSessionManager httpSessionManager;
|
||||
|
||||
public HttpTermServer(String hostIp, int port, long connectionTimeout, EventExecutorGroup workerGroup) {
|
||||
public HttpTermServer(String hostIp, int port, long connectionTimeout, EventExecutorGroup workerGroup, HttpSessionManager httpSessionManager) {
|
||||
this.hostIp = hostIp;
|
||||
this.port = port;
|
||||
this.connectionTimeout = connectionTimeout;
|
||||
this.workerGroup = workerGroup;
|
||||
this.httpSessionManager = httpSessionManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -43,7 +47,7 @@ public class HttpTermServer extends TermServer {
|
||||
@Override
|
||||
public TermServer listen(Handler<Future<TermServer>> listenHandler) {
|
||||
// TODO: charset and inputrc from options
|
||||
bootstrap = new NettyWebsocketTtyBootstrap(workerGroup).setHost(hostIp).setPort(port);
|
||||
bootstrap = new NettyWebsocketTtyBootstrap(workerGroup, httpSessionManager).setHost(hostIp).setPort(port);
|
||||
try {
|
||||
bootstrap.start(new Consumer<TtyConnection>() {
|
||||
@Override
|
||||
|
||||
@@ -221,6 +221,10 @@ public class TermImpl implements Term {
|
||||
}
|
||||
}
|
||||
|
||||
public TtyConnection getConn() {
|
||||
return conn;
|
||||
}
|
||||
|
||||
public void echo(int... codePoints) {
|
||||
Consumer<int[]> out = conn.stdoutHandler();
|
||||
for (int codePoint : codePoints) {
|
||||
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
package com.taobao.arthas.core.shell.term.impl.http;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import javax.security.auth.Subject;
|
||||
|
||||
import com.alibaba.arthas.deps.org.slf4j.Logger;
|
||||
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
|
||||
import com.taobao.arthas.common.ArthasConstants;
|
||||
import com.taobao.arthas.core.security.BasicPrincipal;
|
||||
import com.taobao.arthas.core.security.SecurityAuthenticator;
|
||||
import com.taobao.arthas.core.server.ArthasBootstrap;
|
||||
import com.taobao.arthas.core.shell.term.impl.http.session.HttpSession;
|
||||
import com.taobao.arthas.core.shell.term.impl.http.session.HttpSessionManager;
|
||||
import com.taobao.arthas.core.util.StringUtils;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelDuplexHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.handler.codec.base64.Base64;
|
||||
import io.netty.handler.codec.http.DefaultHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
import io.netty.util.Attribute;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hengyunabc 2021-03-03
|
||||
*
|
||||
*/
|
||||
public final class BasicHttpAuthenticatorHandler extends ChannelDuplexHandler {
|
||||
private static final Logger logger = LoggerFactory.getLogger(BasicHttpAuthenticatorHandler.class);
|
||||
|
||||
private HttpSessionManager httpSessionManager;
|
||||
|
||||
private SecurityAuthenticator securityAuthenticator = ArthasBootstrap.getInstance().getSecurityAuthenticator();
|
||||
|
||||
public BasicHttpAuthenticatorHandler(HttpSessionManager httpSessionManager) {
|
||||
this.httpSessionManager = httpSessionManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
if (!securityAuthenticator.needLogin()) {
|
||||
ctx.fireChannelRead(msg);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean authed = false;
|
||||
if (msg instanceof HttpRequest) {
|
||||
HttpRequest httpRequest = (HttpRequest) msg;
|
||||
|
||||
// 判断session里是否有已登陆信息
|
||||
HttpSession session = httpSessionManager.getOrCreateHttpSession(ctx, httpRequest);
|
||||
if (session != null && session.getAttribute(ArthasConstants.SUBJECT_KEY) != null) {
|
||||
authed = true;
|
||||
}
|
||||
|
||||
// 判断请求header里是否带有 username/password
|
||||
if (!authed) {
|
||||
BasicPrincipal principal = extractBasicAuthSubject(httpRequest);
|
||||
Subject subject = securityAuthenticator.login(principal);
|
||||
if (subject != null) {
|
||||
authed = true;
|
||||
session.setAttribute(ArthasConstants.SUBJECT_KEY, subject);
|
||||
}
|
||||
}
|
||||
|
||||
if (!authed) {
|
||||
// restricted resource, so send back 401 to require valid username/password
|
||||
HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.UNAUTHORIZED);
|
||||
response.headers().set(HttpHeaderNames.WWW_AUTHENTICATE, "Basic realm=\"arthas webconsole\"");
|
||||
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
|
||||
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0);
|
||||
|
||||
ctx.writeAndFlush(response);
|
||||
// close the channel
|
||||
ctx.channel().close();
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
ctx.fireChannelRead(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
||||
if (msg instanceof HttpResponse) {
|
||||
// write cookie
|
||||
HttpResponse response = (HttpResponse) msg;
|
||||
Attribute<HttpSession> attribute = ctx.channel().attr(HttpSessionManager.SESSION_KEY);
|
||||
HttpSession session = attribute.get();
|
||||
if (session != null) {
|
||||
HttpSessionManager.setSessionCookie(response, session);
|
||||
}
|
||||
}
|
||||
super.write(ctx, msg, promise);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the username and password details from the HTTP basic header
|
||||
* Authorization.
|
||||
* <p/>
|
||||
* This requires that the <tt>Authorization</tt> HTTP header is provided, and
|
||||
* its using Basic. Currently Digest is <b>not</b> supported.
|
||||
*
|
||||
* @return {@link HttpPrincipal} with username and password details, or
|
||||
* <tt>null</tt> if not possible to extract
|
||||
*/
|
||||
protected static BasicPrincipal extractBasicAuthSubject(HttpRequest request) {
|
||||
String auth = request.headers().get(HttpHeaderNames.AUTHORIZATION);
|
||||
if (auth != null) {
|
||||
String constraint = StringUtils.before(auth, " ");
|
||||
if (constraint != null) {
|
||||
if ("Basic".equalsIgnoreCase(constraint.trim())) {
|
||||
String decoded = StringUtils.after(auth, " ");
|
||||
// the decoded part is base64 encoded, so we need to decode that
|
||||
ByteBuf buf = Unpooled.wrappedBuffer(decoded.getBytes());
|
||||
ByteBuf out = Base64.decode(buf);
|
||||
String userAndPw = out.toString(Charset.defaultCharset());
|
||||
String username = StringUtils.before(userAndPw, ":");
|
||||
String password = StringUtils.after(userAndPw, ":");
|
||||
BasicPrincipal principal = new BasicPrincipal(username, password);
|
||||
logger.debug("Extracted Basic Auth principal from HTTP header: {}", principal);
|
||||
return principal;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
package com.taobao.arthas.core.shell.term.impl.http;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.taobao.arthas.common.ArthasConstants;
|
||||
import com.taobao.arthas.core.shell.term.impl.http.session.HttpSession;
|
||||
import com.taobao.arthas.core.shell.term.impl.http.session.HttpSessionManager;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||
import io.termd.core.http.HttpTtyConnection;
|
||||
|
||||
/**
|
||||
* 从http请求传递过来的 session 信息。解析websocket创建的 term 还需要登陆验证问题
|
||||
*
|
||||
* @author hengyunabc 2021-03-04
|
||||
*
|
||||
*/
|
||||
public class ExtHttpTtyConnection extends HttpTtyConnection {
|
||||
private ChannelHandlerContext context;
|
||||
|
||||
public ExtHttpTtyConnection(ChannelHandlerContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void write(byte[] buffer) {
|
||||
ByteBuf byteBuf = Unpooled.buffer();
|
||||
byteBuf.writeBytes(buffer);
|
||||
if (context != null) {
|
||||
context.writeAndFlush(new TextWebSocketFrame(byteBuf));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void schedule(Runnable task, long delay, TimeUnit unit) {
|
||||
if (context != null) {
|
||||
context.executor().schedule(task, delay, unit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable task) {
|
||||
if (context != null) {
|
||||
context.executor().execute(task);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (context != null) {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
|
||||
public Map<String, Object> extSessions() {
|
||||
if (context != null) {
|
||||
HttpSession httpSession = HttpSessionManager.getHttpSessionFromContext(context);
|
||||
if (httpSession != null) {
|
||||
Object subject = httpSession.getAttribute(ArthasConstants.SUBJECT_KEY);
|
||||
if (subject != null) {
|
||||
Map<String, Object> result = new HashMap<String, Object>();
|
||||
result.put(ArthasConstants.SUBJECT_KEY, subject);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
}
|
||||
+1
-1
@@ -77,7 +77,7 @@ public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequ
|
||||
try {
|
||||
//handle http restful api
|
||||
if ("/api".equals(path)) {
|
||||
response = httpApiHandler.handle(request);
|
||||
response = httpApiHandler.handle(ctx, request);
|
||||
isHttpApiResponse = true;
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -1,6 +1,7 @@
|
||||
package com.taobao.arthas.core.shell.term.impl.http;
|
||||
|
||||
import com.taobao.arthas.common.ArthasConstants;
|
||||
import com.taobao.arthas.core.shell.term.impl.http.session.HttpSessionManager;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.Channel;
|
||||
@@ -37,11 +38,13 @@ public class NettyWebsocketTtyBootstrap {
|
||||
private EventLoopGroup group;
|
||||
private Channel channel;
|
||||
private EventExecutorGroup workerGroup;
|
||||
private HttpSessionManager httpSessionManager;
|
||||
|
||||
public NettyWebsocketTtyBootstrap(EventExecutorGroup workerGroup) {
|
||||
public NettyWebsocketTtyBootstrap(EventExecutorGroup workerGroup, HttpSessionManager httpSessionManager) {
|
||||
this.workerGroup = workerGroup;
|
||||
this.host = "localhost";
|
||||
this.port = 8080;
|
||||
this.httpSessionManager = httpSessionManager;
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
@@ -68,7 +71,7 @@ public class NettyWebsocketTtyBootstrap {
|
||||
if (this.port > 0) {
|
||||
ServerBootstrap b = new ServerBootstrap();
|
||||
b.group(group).channel(NioServerSocketChannel.class).handler(new LoggingHandler(LogLevel.INFO))
|
||||
.childHandler(new TtyServerInitializer(channelGroup, handler, workerGroup));
|
||||
.childHandler(new TtyServerInitializer(channelGroup, handler, workerGroup, httpSessionManager));
|
||||
|
||||
final ChannelFuture f = b.bind(host, port);
|
||||
f.addListener(new GenericFutureListener<Future<? super Void>>() {
|
||||
|
||||
+8
-6
@@ -1,5 +1,8 @@
|
||||
package com.taobao.arthas.core.shell.term.impl.http;
|
||||
|
||||
import com.taobao.arthas.common.ArthasConstants;
|
||||
import com.taobao.arthas.core.shell.term.impl.http.session.HttpSessionManager;
|
||||
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.group.ChannelGroup;
|
||||
@@ -13,10 +16,6 @@ import io.netty.util.concurrent.EventExecutorGroup;
|
||||
import io.termd.core.function.Consumer;
|
||||
import io.termd.core.tty.TtyConnection;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import com.taobao.arthas.common.ArthasConstants;
|
||||
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
|
||||
@@ -26,11 +25,13 @@ public class TtyServerInitializer extends ChannelInitializer<SocketChannel> {
|
||||
private final ChannelGroup group;
|
||||
private final Consumer<TtyConnection> handler;
|
||||
private EventExecutorGroup workerGroup;
|
||||
private HttpSessionManager httpSessionManager;
|
||||
|
||||
public TtyServerInitializer(ChannelGroup group, Consumer<TtyConnection> handler, EventExecutorGroup workerGroup) {
|
||||
public TtyServerInitializer(ChannelGroup group, Consumer<TtyConnection> handler, EventExecutorGroup workerGroup, HttpSessionManager httpSessionManager) {
|
||||
this.group = group;
|
||||
this.handler = handler;
|
||||
this.workerGroup = workerGroup;
|
||||
this.workerGroup = workerGroup;
|
||||
this.httpSessionManager = httpSessionManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -40,6 +41,7 @@ public class TtyServerInitializer extends ChannelInitializer<SocketChannel> {
|
||||
pipeline.addLast(new HttpServerCodec());
|
||||
pipeline.addLast(new ChunkedWriteHandler());
|
||||
pipeline.addLast(new HttpObjectAggregator(ArthasConstants.MAX_HTTP_CONTENT_LENGTH));
|
||||
pipeline.addLast(new BasicHttpAuthenticatorHandler(httpSessionManager));
|
||||
pipeline.addLast(workerGroup, "HttpRequestHandler", new HttpRequestHandler("/ws"));
|
||||
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
|
||||
pipeline.addLast(new IdleStateHandler(0, 0, ArthasConstants.WEBSOCKET_IDLE_SECONDS));
|
||||
|
||||
+1
-34
@@ -16,8 +16,6 @@
|
||||
|
||||
package com.taobao.arthas.core.shell.term.impl.http;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.channel.group.ChannelGroup;
|
||||
@@ -29,7 +27,6 @@ import io.termd.core.function.Consumer;
|
||||
import io.termd.core.http.HttpTtyConnection;
|
||||
import io.termd.core.tty.TtyConnection;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
|
||||
@@ -57,37 +54,7 @@ public class TtyWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWe
|
||||
if (evt == WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE) {
|
||||
ctx.pipeline().remove(HttpRequestHandler.class);
|
||||
group.add(ctx.channel());
|
||||
conn = new HttpTtyConnection() {
|
||||
@Override
|
||||
protected void write(byte[] buffer) {
|
||||
ByteBuf byteBuf = Unpooled.buffer();
|
||||
byteBuf.writeBytes(buffer);
|
||||
if (context != null) {
|
||||
context.writeAndFlush(new TextWebSocketFrame(byteBuf));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void schedule(Runnable task, long delay, TimeUnit unit) {
|
||||
if (context != null) {
|
||||
context.executor().schedule(task, delay, unit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable task) {
|
||||
if (context != null) {
|
||||
context.executor().execute(task);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (context != null) {
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
conn = new ExtHttpTtyConnection(context);
|
||||
handler.accept(conn);
|
||||
} else if (evt instanceof IdleStateEvent) {
|
||||
ctx.writeAndFlush(new PingWebSocketFrame());
|
||||
|
||||
+17
-5
@@ -3,6 +3,7 @@ package com.taobao.arthas.core.shell.term.impl.http.api;
|
||||
import com.alibaba.arthas.deps.org.slf4j.Logger;
|
||||
import com.alibaba.arthas.deps.org.slf4j.LoggerFactory;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.taobao.arthas.common.ArthasConstants;
|
||||
import com.taobao.arthas.common.PidUtils;
|
||||
import com.taobao.arthas.core.command.model.*;
|
||||
import com.taobao.arthas.core.distribution.PackingResultDistributor;
|
||||
@@ -25,6 +26,8 @@ import com.taobao.arthas.core.shell.system.JobListener;
|
||||
import com.taobao.arthas.core.shell.system.impl.InternalCommandManager;
|
||||
import com.taobao.arthas.core.shell.term.SignalHandler;
|
||||
import com.taobao.arthas.core.shell.term.Term;
|
||||
import com.taobao.arthas.core.shell.term.impl.http.session.HttpSession;
|
||||
import com.taobao.arthas.core.shell.term.impl.http.session.HttpSessionManager;
|
||||
import com.taobao.arthas.core.util.ArthasBanner;
|
||||
import com.taobao.arthas.core.util.DateUtils;
|
||||
import com.taobao.arthas.core.util.JsonUtils;
|
||||
@@ -32,6 +35,7 @@ import com.taobao.arthas.core.util.StringUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufOutputStream;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.*;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import io.termd.core.function.Function;
|
||||
@@ -42,7 +46,6 @@ import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
|
||||
/**
|
||||
@@ -55,7 +58,6 @@ public class HttpApiHandler {
|
||||
private static final Logger logger = LoggerFactory.getLogger(HttpApiHandler.class);
|
||||
public static final int DEFAULT_EXEC_TIMEOUT = 30000;
|
||||
private final SessionManager sessionManager;
|
||||
private static HttpApiHandler instance;
|
||||
private final InternalCommandManager commandManager;
|
||||
private final JobController jobController;
|
||||
private final HistoryManager historyManager;
|
||||
@@ -81,7 +83,7 @@ public class HttpApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
public HttpResponse handle(FullHttpRequest request) throws Exception {
|
||||
public HttpResponse handle(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
|
||||
|
||||
ApiResponse result;
|
||||
String requestBody = null;
|
||||
@@ -92,7 +94,7 @@ public class HttpApiHandler {
|
||||
requestBody = getBody(request);
|
||||
ApiRequest apiRequest = parseRequest(requestBody);
|
||||
requestId = apiRequest.getRequestId();
|
||||
result = processRequest(apiRequest);
|
||||
result = processRequest(ctx, apiRequest);
|
||||
} else {
|
||||
result = createResponse(ApiState.REFUSED, "Unsupported http method: " + method.name());
|
||||
}
|
||||
@@ -191,7 +193,7 @@ public class HttpApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private ApiResponse processRequest(ApiRequest apiRequest) {
|
||||
private ApiResponse processRequest(ChannelHandlerContext ctx, ApiRequest apiRequest) {
|
||||
|
||||
String actionStr = apiRequest.getAction();
|
||||
try {
|
||||
@@ -226,6 +228,16 @@ public class HttpApiHandler {
|
||||
sessionManager.updateAccessTime(session);
|
||||
}
|
||||
|
||||
// 请求到达这里,如果有需要鉴权,则已经在前面的handler里处理过了
|
||||
// 如果有鉴权取到的 Subject,则传递到 arthas的session里
|
||||
HttpSession httpSession = HttpSessionManager.getHttpSessionFromContext(ctx);
|
||||
if (httpSession != null) {
|
||||
Object subject = httpSession.getAttribute(ArthasConstants.SUBJECT_KEY);
|
||||
if (subject != null) {
|
||||
session.put(ArthasConstants.SUBJECT_KEY, subject);
|
||||
}
|
||||
}
|
||||
|
||||
//dispatch requests
|
||||
ApiResponse response = dispatchRequest(action, apiRequest, session);
|
||||
if (response != null) {
|
||||
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
|
||||
package com.taobao.arthas.core.shell.term.impl.http.session;
|
||||
|
||||
import java.util.Enumeration;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hengyunabc 2021-03-03
|
||||
*
|
||||
*/
|
||||
public interface HttpSession {
|
||||
|
||||
/**
|
||||
* Returns the time when this session was created, measured in milliseconds
|
||||
* since midnight January 1, 1970 GMT.
|
||||
*
|
||||
* @return a <code>long</code> specifying when this session was created,
|
||||
* expressed in milliseconds since 1/1/1970 GMT
|
||||
* @exception IllegalStateException if this method is called on an invalidated
|
||||
* session
|
||||
*/
|
||||
public long getCreationTime();
|
||||
|
||||
/**
|
||||
* Returns a string containing the unique identifier assigned to this session.
|
||||
* The identifier is assigned by the servlet container and is implementation
|
||||
* dependent.
|
||||
*
|
||||
* @return a string specifying the identifier assigned to this session
|
||||
* @exception IllegalStateException if this method is called on an invalidated
|
||||
* session
|
||||
*/
|
||||
public String getId();
|
||||
|
||||
/**
|
||||
* Returns the last time the client sent a request associated with this session,
|
||||
* as the number of milliseconds since midnight January 1, 1970 GMT, and marked
|
||||
* by the time the container received the request.
|
||||
* <p>
|
||||
* Actions that your application takes, such as getting or setting a value
|
||||
* associated with the session, do not affect the access time.
|
||||
*
|
||||
* @return a <code>long</code> representing the last time the client sent a
|
||||
* request associated with this session, expressed in milliseconds since
|
||||
* 1/1/1970 GMT
|
||||
* @exception IllegalStateException if this method is called on an invalidated
|
||||
* session
|
||||
*/
|
||||
public long getLastAccessedTime();
|
||||
|
||||
/**
|
||||
* Specifies the time, in seconds, between client requests before the servlet
|
||||
* container will invalidate this session. A zero or negative time indicates
|
||||
* that the session should never timeout.
|
||||
*
|
||||
* @param interval An integer specifying the number of seconds
|
||||
*/
|
||||
public void setMaxInactiveInterval(int interval);
|
||||
|
||||
/**
|
||||
* Returns the maximum time interval, in seconds, that the servlet container
|
||||
* will keep this session open between client accesses. After this interval, the
|
||||
* servlet container will invalidate the session. The maximum time interval can
|
||||
* be set with the <code>setMaxInactiveInterval</code> method. A zero or
|
||||
* negative time indicates that the session should never timeout.
|
||||
*
|
||||
* @return an integer specifying the number of seconds this session remains open
|
||||
* between client requests
|
||||
* @see #setMaxInactiveInterval
|
||||
*/
|
||||
public int getMaxInactiveInterval();
|
||||
|
||||
/**
|
||||
* Returns the object bound with the specified name in this session, or
|
||||
* <code>null</code> if no object is bound under the name.
|
||||
*
|
||||
* @param name a string specifying the name of the object
|
||||
* @return the object with the specified name
|
||||
* @exception IllegalStateException if this method is called on an invalidated
|
||||
* session
|
||||
*/
|
||||
public Object getAttribute(String name);
|
||||
|
||||
/**
|
||||
* Returns an <code>Enumeration</code> of <code>String</code> objects containing
|
||||
* the names of all the objects bound to this session.
|
||||
*
|
||||
* @return an <code>Enumeration</code> of <code>String</code> objects specifying
|
||||
* the names of all the objects bound to this session
|
||||
* @exception IllegalStateException if this method is called on an invalidated
|
||||
* session
|
||||
*/
|
||||
public Enumeration<String> getAttributeNames();
|
||||
|
||||
/**
|
||||
* Binds an object to this session, using the name specified. If an object of
|
||||
* the same name is already bound to the session, the object is replaced.
|
||||
* <p>
|
||||
* After this method executes, and if the new object implements
|
||||
* <code>HttpSessionBindingListener</code>, the container calls
|
||||
* <code>HttpSessionBindingListener.valueBound</code>. The container then
|
||||
* notifies any <code>HttpSessionAttributeListener</code>s in the web
|
||||
* application.
|
||||
* <p>
|
||||
* If an object was already bound to this session of this name that implements
|
||||
* <code>HttpSessionBindingListener</code>, its
|
||||
* <code>HttpSessionBindingListener.valueUnbound</code> method is called.
|
||||
* <p>
|
||||
* If the value passed in is null, this has the same effect as calling
|
||||
* <code>removeAttribute()</code>.
|
||||
*
|
||||
* @param name the name to which the object is bound; cannot be null
|
||||
* @param value the object to be bound
|
||||
* @exception IllegalStateException if this method is called on an invalidated
|
||||
* session
|
||||
*/
|
||||
public void setAttribute(String name, Object value);
|
||||
|
||||
/**
|
||||
* Removes the object bound with the specified name from this session. If the
|
||||
* session does not have an object bound with the specified name, this method
|
||||
* does nothing.
|
||||
* <p>
|
||||
* After this method executes, and if the object implements
|
||||
* <code>HttpSessionBindingListener</code>, the container calls
|
||||
* <code>HttpSessionBindingListener.valueUnbound</code>. The container then
|
||||
* notifies any <code>HttpSessionAttributeListener</code>s in the web
|
||||
* application.
|
||||
*
|
||||
* @param name the name of the object to remove from this session
|
||||
* @exception IllegalStateException if this method is called on an invalidated
|
||||
* session
|
||||
*/
|
||||
public void removeAttribute(String name);
|
||||
|
||||
/**
|
||||
* Invalidates this session then unbinds any objects bound to it.
|
||||
*
|
||||
* @exception IllegalStateException if this method is called on an already
|
||||
* invalidated session
|
||||
*/
|
||||
public void invalidate();
|
||||
|
||||
/**
|
||||
* Returns <code>true</code> if the client does not yet know about the session
|
||||
* or if the client chooses not to join the session. For example, if the server
|
||||
* used only cookie-based sessions, and the client had disabled the use of
|
||||
* cookies, then a session would be new on each request.
|
||||
*
|
||||
* @return <code>true</code> if the server has created a session, but the client
|
||||
* has not yet joined
|
||||
* @exception IllegalStateException if this method is called on an already
|
||||
* invalidated session
|
||||
*/
|
||||
public boolean isNew();
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
package com.taobao.arthas.core.shell.term.impl.http.session;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import com.taobao.arthas.common.ArthasConstants;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.codec.http.cookie.Cookie;
|
||||
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
|
||||
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
|
||||
import io.netty.util.Attribute;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
/**
|
||||
* <pre>
|
||||
* netty里的http session管理。因为同一域名的不同端口共享cookie,所以需要共用。
|
||||
* </pre>
|
||||
*
|
||||
* @author hengyunabc 2021-03-03
|
||||
*
|
||||
*/
|
||||
public class HttpSessionManager {
|
||||
public static AttributeKey<HttpSession> SESSION_KEY = AttributeKey.valueOf("session");
|
||||
|
||||
private LRUCache<String, HttpSession> sessions = new LRUCache<String, HttpSession>(1024);
|
||||
|
||||
public HttpSessionManager() {
|
||||
|
||||
}
|
||||
|
||||
private HttpSession getSession(HttpRequest httpRequest) {
|
||||
// TODO 增加从 url中获取 session id 功能?
|
||||
|
||||
Set<Cookie> cookies;
|
||||
String value = httpRequest.headers().get(HttpHeaderNames.COOKIE);
|
||||
if (value == null) {
|
||||
cookies = Collections.emptySet();
|
||||
} else {
|
||||
cookies = ServerCookieDecoder.STRICT.decode(value);
|
||||
}
|
||||
for (Cookie cookie : cookies) {
|
||||
if (ArthasConstants.ASESSION_KEY.equals(cookie.name())) {
|
||||
String sessionId = cookie.value();
|
||||
return sessions.get(sessionId);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static HttpSession getHttpSessionFromContext(ChannelHandlerContext ctx) {
|
||||
return ctx.channel().attr(SESSION_KEY).get();
|
||||
}
|
||||
|
||||
public HttpSession getOrCreateHttpSession(ChannelHandlerContext ctx, HttpRequest httpRequest) {
|
||||
// 尝试用 ctx 和从 cookie里读取出 session
|
||||
Attribute<HttpSession> attribute = ctx.channel().attr(SESSION_KEY);
|
||||
HttpSession httpSession = attribute.get();
|
||||
if (httpSession != null) {
|
||||
return httpSession;
|
||||
}
|
||||
httpSession = getSession(httpRequest);
|
||||
if (httpSession != null) {
|
||||
attribute.set(httpSession);
|
||||
return httpSession;
|
||||
}
|
||||
// 创建session,并设置到ctx里
|
||||
httpSession = newHttpSession();
|
||||
attribute.set(httpSession);
|
||||
return httpSession;
|
||||
}
|
||||
|
||||
private HttpSession newHttpSession() {
|
||||
SimpleHttpSession session = new SimpleHttpSession();
|
||||
this.sessions.put(session.getId(), session);
|
||||
return session;
|
||||
}
|
||||
|
||||
public static void setSessionCookie(HttpResponse response, HttpSession session) {
|
||||
response.headers().add(HttpHeaderNames.SET_COOKIE,
|
||||
ServerCookieEncoder.STRICT.encode(ArthasConstants.ASESSION_KEY, session.getId()));
|
||||
}
|
||||
|
||||
public void start() {
|
||||
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
sessions.clear();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.taobao.arthas.core.shell.term.impl.http.session;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* An LRU cache, based on <code>LinkedHashMap</code>.
|
||||
*
|
||||
* <p>
|
||||
* This cache has a fixed maximum number of elements (<code>cacheSize</code>).
|
||||
* If the cache is full and another entry is added, the LRU (least recently
|
||||
* used) entry is dropped.
|
||||
*
|
||||
* <p>
|
||||
* This class is thread-safe. All methods of this class are synchronized.
|
||||
*
|
||||
* <p>
|
||||
* Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland<br>
|
||||
* Multi-licensed: EPL / LGPL / GPL / AL / BSD.
|
||||
*/
|
||||
public class LRUCache<K, V> {
|
||||
|
||||
private static final float hashTableLoadFactor = 0.75f;
|
||||
|
||||
private LinkedHashMap<K, V> map;
|
||||
private int cacheSize;
|
||||
|
||||
/**
|
||||
* Creates a new LRU cache.
|
||||
*
|
||||
* @param cacheSize the maximum number of entries that will be kept in this
|
||||
* cache.
|
||||
*/
|
||||
public LRUCache(int cacheSize) {
|
||||
this.cacheSize = cacheSize;
|
||||
int hashTableCapacity = (int) Math.ceil(cacheSize / hashTableLoadFactor) + 1;
|
||||
map = new LinkedHashMap<K, V>(hashTableCapacity, hashTableLoadFactor, true) {
|
||||
// (an anonymous inner class)
|
||||
private static final long serialVersionUID = 1;
|
||||
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
|
||||
return size() > LRUCache.this.cacheSize;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an entry from the cache.<br>
|
||||
* The retrieved entry becomes the MRU (most recently used) entry.
|
||||
*
|
||||
* @param key the key whose associated value is to be returned.
|
||||
* @return the value associated to this key, or null if no value with this key
|
||||
* exists in the cache.
|
||||
*/
|
||||
public synchronized V get(K key) {
|
||||
return map.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an entry to this cache. The new entry becomes the MRU (most recently
|
||||
* used) entry. If an entry with the specified key already exists in the cache,
|
||||
* it is replaced by the new entry. If the cache is full, the LRU (least
|
||||
* recently used) entry is removed from the cache.
|
||||
*
|
||||
* @param key the key with which the specified value is to be associated.
|
||||
* @param value a value to be associated with the specified key.
|
||||
*/
|
||||
public synchronized void put(K key, V value) {
|
||||
map.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the cache.
|
||||
*/
|
||||
public synchronized void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of used entries in the cache.
|
||||
*
|
||||
* @return the number of entries currently in the cache.
|
||||
*/
|
||||
public synchronized int usedEntries() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a <code>Collection</code> that contains a copy of all cache entries.
|
||||
*
|
||||
* @return a <code>Collection</code> with a copy of the cache content.
|
||||
*/
|
||||
public synchronized Collection<Map.Entry<K, V>> getAll() {
|
||||
return new ArrayList<Map.Entry<K, V>>(map.entrySet());
|
||||
}
|
||||
|
||||
} // end class LRUCache
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package com.taobao.arthas.core.shell.term.impl.http.session;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import com.taobao.arthas.core.util.StringUtils;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hengyunabc 2021-03-03
|
||||
*
|
||||
*/
|
||||
public class SimpleHttpSession implements HttpSession {
|
||||
private Map<String, Object> attributes = new ConcurrentHashMap<String, Object>();
|
||||
|
||||
private String id;
|
||||
|
||||
public SimpleHttpSession() {
|
||||
id = StringUtils.randomString(32);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getCreationTime() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLastAccessedTime() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setMaxInactiveInterval(int interval) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMaxInactiveInterval() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getAttribute(String name) {
|
||||
return attributes.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enumeration<String> getAttributeNames() {
|
||||
return Collections.enumeration(this.attributes.keySet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAttribute(String name, Object value) {
|
||||
attributes.put(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAttribute(String name) {
|
||||
attributes.remove(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNew() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
+5
-2
@@ -10,6 +10,7 @@ import com.taobao.arthas.core.shell.term.Term;
|
||||
import com.taobao.arthas.core.shell.term.TermServer;
|
||||
import com.taobao.arthas.core.shell.term.impl.Helper;
|
||||
import com.taobao.arthas.core.shell.term.impl.TermImpl;
|
||||
import com.taobao.arthas.core.shell.term.impl.http.session.HttpSessionManager;
|
||||
|
||||
import io.netty.util.concurrent.EventExecutorGroup;
|
||||
import io.termd.core.function.Consumer;
|
||||
@@ -31,12 +32,14 @@ public class HttpTelnetTermServer extends TermServer {
|
||||
private int port;
|
||||
private long connectionTimeout;
|
||||
private EventExecutorGroup workerGroup;
|
||||
private HttpSessionManager httpSessionManager;
|
||||
|
||||
public HttpTelnetTermServer(String hostIp, int port, long connectionTimeout, EventExecutorGroup workerGroup) {
|
||||
public HttpTelnetTermServer(String hostIp, int port, long connectionTimeout, EventExecutorGroup workerGroup, HttpSessionManager httpSessionManager) {
|
||||
this.hostIp = hostIp;
|
||||
this.port = port;
|
||||
this.connectionTimeout = connectionTimeout;
|
||||
this.workerGroup = workerGroup;
|
||||
this.httpSessionManager = httpSessionManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -48,7 +51,7 @@ public class HttpTelnetTermServer extends TermServer {
|
||||
@Override
|
||||
public TermServer listen(Handler<Future<TermServer>> listenHandler) {
|
||||
// TODO: charset and inputrc from options
|
||||
bootstrap = new NettyHttpTelnetTtyBootstrap(workerGroup).setHost(hostIp).setPort(port);
|
||||
bootstrap = new NettyHttpTelnetTtyBootstrap(workerGroup, httpSessionManager).setHost(hostIp).setPort(port);
|
||||
try {
|
||||
bootstrap.start(new Consumer<TtyConnection>() {
|
||||
@Override
|
||||
|
||||
+6
-2
@@ -1,5 +1,7 @@
|
||||
package com.taobao.arthas.core.shell.term.impl.httptelnet;
|
||||
|
||||
import com.taobao.arthas.core.shell.term.impl.http.session.HttpSessionManager;
|
||||
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelOption;
|
||||
@@ -31,11 +33,13 @@ public class NettyHttpTelnetBootstrap extends TelnetBootstrap {
|
||||
private EventLoopGroup group;
|
||||
private ChannelGroup channelGroup;
|
||||
private EventExecutorGroup workerGroup;
|
||||
private HttpSessionManager httpSessionManager;
|
||||
|
||||
public NettyHttpTelnetBootstrap(EventExecutorGroup workerGroup) {
|
||||
public NettyHttpTelnetBootstrap(EventExecutorGroup workerGroup, HttpSessionManager httpSessionManager) {
|
||||
this.workerGroup = workerGroup;
|
||||
this.group = new NioEventLoopGroup(new DefaultThreadFactory("arthas-NettyHttpTelnetBootstrap", true));
|
||||
this.channelGroup = new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE);
|
||||
this.httpSessionManager = httpSessionManager;
|
||||
}
|
||||
|
||||
public NettyHttpTelnetBootstrap setHost(String host) {
|
||||
@@ -60,7 +64,7 @@ public class NettyHttpTelnetBootstrap extends TelnetBootstrap {
|
||||
.childHandler(new ChannelInitializer<SocketChannel>() {
|
||||
@Override
|
||||
public void initChannel(SocketChannel ch) throws Exception {
|
||||
ch.pipeline().addLast(new ProtocolDetectHandler(channelGroup, handlerFactory, factory, workerGroup));
|
||||
ch.pipeline().addLast(new ProtocolDetectHandler(channelGroup, handlerFactory, factory, workerGroup, httpSessionManager));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+4
-2
@@ -2,6 +2,8 @@ package com.taobao.arthas.core.shell.term.impl.httptelnet;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import com.taobao.arthas.core.shell.term.impl.http.session.HttpSessionManager;
|
||||
|
||||
import io.netty.util.concurrent.EventExecutorGroup;
|
||||
import io.termd.core.function.Consumer;
|
||||
import io.termd.core.function.Supplier;
|
||||
@@ -22,8 +24,8 @@ public class NettyHttpTelnetTtyBootstrap {
|
||||
private boolean inBinary;
|
||||
private Charset charset = Charset.forName("UTF-8");
|
||||
|
||||
public NettyHttpTelnetTtyBootstrap(EventExecutorGroup workerGroup) {
|
||||
this.httpTelnetTtyBootstrap = new NettyHttpTelnetBootstrap(workerGroup);
|
||||
public NettyHttpTelnetTtyBootstrap(EventExecutorGroup workerGroup, HttpSessionManager httpSessionManager) {
|
||||
this.httpTelnetTtyBootstrap = new NettyHttpTelnetBootstrap(workerGroup, httpSessionManager);
|
||||
}
|
||||
|
||||
public String getHost() {
|
||||
|
||||
+8
-2
@@ -1,12 +1,14 @@
|
||||
package com.taobao.arthas.core.shell.term.impl.httptelnet;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.taobao.arthas.common.ArthasConstants;
|
||||
import com.taobao.arthas.core.shell.term.impl.http.BasicHttpAuthenticatorHandler;
|
||||
import com.taobao.arthas.core.shell.term.impl.http.HttpRequestHandler;
|
||||
|
||||
import com.taobao.arthas.core.shell.term.impl.http.TtyWebSocketFrameHandler;
|
||||
import com.taobao.arthas.core.shell.term.impl.http.session.HttpSessionManager;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
@@ -35,13 +37,16 @@ public class ProtocolDetectHandler extends ChannelInboundHandlerAdapter {
|
||||
private Supplier<TelnetHandler> handlerFactory;
|
||||
private Consumer<TtyConnection> ttyConnectionFactory;
|
||||
private EventExecutorGroup workerGroup;
|
||||
private HttpSessionManager httpSessionManager;
|
||||
|
||||
public ProtocolDetectHandler(ChannelGroup channelGroup, final Supplier<TelnetHandler> handlerFactory,
|
||||
Consumer<TtyConnection> ttyConnectionFactory, EventExecutorGroup workerGroup) {
|
||||
Consumer<TtyConnection> ttyConnectionFactory, EventExecutorGroup workerGroup,
|
||||
HttpSessionManager httpSessionManager) {
|
||||
this.channelGroup = channelGroup;
|
||||
this.handlerFactory = handlerFactory;
|
||||
this.ttyConnectionFactory = ttyConnectionFactory;
|
||||
this.workerGroup = workerGroup;
|
||||
this.httpSessionManager = httpSessionManager;
|
||||
}
|
||||
|
||||
private ScheduledFuture<?> detectTelnetFuture;
|
||||
@@ -87,6 +92,7 @@ public class ProtocolDetectHandler extends ChannelInboundHandlerAdapter {
|
||||
pipeline.addLast(new HttpServerCodec());
|
||||
pipeline.addLast(new ChunkedWriteHandler());
|
||||
pipeline.addLast(new HttpObjectAggregator(ArthasConstants.MAX_HTTP_CONTENT_LENGTH));
|
||||
pipeline.addLast(new BasicHttpAuthenticatorHandler(httpSessionManager));
|
||||
pipeline.addLast(workerGroup, "HttpRequestHandler", new HttpRequestHandler("/ws"));
|
||||
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
|
||||
pipeline.addLast(new IdleStateHandler(0, 0, ArthasConstants.WEBSOCKET_IDLE_SECONDS));
|
||||
|
||||
@@ -13,6 +13,7 @@ import java.util.StringTokenizer;
|
||||
import java.util.TreeSet;
|
||||
|
||||
public abstract class StringUtils {
|
||||
private static final String AB = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
/**
|
||||
* 获取异常的原因描述
|
||||
@@ -912,4 +913,40 @@ public abstract class StringUtils {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static String randomString(int length) {
|
||||
StringBuilder sb = new StringBuilder(length);
|
||||
for (int i = 0; i < length; i++)
|
||||
sb.append(AB.charAt(ThreadLocalRandom.current().nextInt(AB.length())));
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string before the given token
|
||||
*
|
||||
* @param text the text
|
||||
* @param before the token
|
||||
* @return the text before the token, or <tt>null</tt> if text does not contain
|
||||
* the token
|
||||
*/
|
||||
public static String before(String text, String before) {
|
||||
int pos = text.indexOf(before);
|
||||
return pos == -1 ? null : text.substring(0, pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string after the given token
|
||||
*
|
||||
* @param text the text
|
||||
* @param after the token
|
||||
* @return the text after the token, or <tt>null</tt> if text does not contain
|
||||
* the token
|
||||
*/
|
||||
public static String after(String text, String after) {
|
||||
int pos = text.indexOf(after);
|
||||
if (pos == -1) {
|
||||
return null;
|
||||
}
|
||||
return text.substring(pos + after.length());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user