mirror of
https://github.com/alibaba/arthas.git
synced 2024-04-21 10:21:39 +00:00
supoort http visit arthas-output directory. #921
This commit is contained in:
@@ -9,7 +9,6 @@ import com.taobao.arthas.core.shell.command.CommandResolver;
|
||||
import com.taobao.arthas.core.shell.handlers.BindHandler;
|
||||
import com.taobao.arthas.core.shell.impl.ShellServerImpl;
|
||||
import com.taobao.arthas.core.shell.term.impl.HttpTermServer;
|
||||
import com.taobao.arthas.core.shell.term.impl.TelnetTermServer;
|
||||
import com.taobao.arthas.core.shell.term.impl.httptelnet.HttpTelnetTermServer;
|
||||
import com.taobao.arthas.core.util.ArthasBanner;
|
||||
import com.taobao.arthas.core.util.Constants;
|
||||
@@ -19,6 +18,7 @@ import com.taobao.middleware.logger.Logger;
|
||||
|
||||
import io.netty.channel.ChannelFuture;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.instrument.Instrumentation;
|
||||
import java.lang.reflect.Method;
|
||||
@@ -50,10 +50,16 @@ public class ArthasBootstrap {
|
||||
private ExecutorService executorService;
|
||||
private TunnelClient tunnelClient;
|
||||
|
||||
private File arthasOutputDir;
|
||||
|
||||
private ArthasBootstrap(int pid, Instrumentation instrumentation) {
|
||||
this.pid = pid;
|
||||
this.instrumentation = instrumentation;
|
||||
|
||||
String outputPath = System.getProperty("arthas.output.dir", "arthas-output");
|
||||
arthasOutputDir = new File(outputPath);
|
||||
arthasOutputDir.mkdirs();
|
||||
|
||||
executorService = Executors.newCachedThreadPool(new ThreadFactory() {
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
package com.taobao.arthas.core.shell.term.impl.httptelnet;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
|
||||
import com.taobao.arthas.common.IOUtils;
|
||||
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpUtil;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author hengyunabc 2019-11-06
|
||||
*
|
||||
*/
|
||||
public class DirectoryBrowser {
|
||||
|
||||
//@formatter:off
|
||||
private static String pageHeader = "<!DOCTYPE html>\n" +
|
||||
"<html>\n" +
|
||||
"\n" +
|
||||
"<head>\n" +
|
||||
" <title>Arthas Resouces: %s</title>\n" +
|
||||
" <meta charset=\"utf-8\" name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n" +
|
||||
" <style>\n" +
|
||||
"body {\n" +
|
||||
" background: #fff;\n" +
|
||||
"}\n" +
|
||||
" </style>\n" +
|
||||
"</head>\n" +
|
||||
"\n" +
|
||||
"<body>\n" +
|
||||
" <header>\n" +
|
||||
" <h1>%s</h1>\n" +
|
||||
" </header>\n" +
|
||||
" <hr/>\n" +
|
||||
" <main>\n" +
|
||||
" <pre id=\"contents\">\n";
|
||||
|
||||
private static String pageFooter = " </pre>\n" +
|
||||
" </main>\n" +
|
||||
" <hr/>\n" +
|
||||
"</body>\n" +
|
||||
"\n" +
|
||||
"</html>";
|
||||
//@formatter:on
|
||||
|
||||
private static String linePart1Str = "<a href=\"%s\" title=\"%s\">";
|
||||
private static String linePart2Str = "%-60s";
|
||||
|
||||
private static String renderDir(File dir) {
|
||||
File[] listFiles = dir.listFiles();
|
||||
|
||||
StringBuilder sb = new StringBuilder(8192);
|
||||
String dirName = dir.getName() + "/";
|
||||
sb.append(String.format(pageHeader, dirName, dirName));
|
||||
|
||||
sb.append("<a href=\"../\" title=\"../\">../</a>\n");
|
||||
|
||||
if (listFiles != null) {
|
||||
Arrays.sort(listFiles);
|
||||
for (File f : listFiles) {
|
||||
if (f.isDirectory()) {
|
||||
String name = f.getName() + "/";
|
||||
String part1Format = String.format(linePart1Str, name, name, name);
|
||||
sb.append(part1Format);
|
||||
|
||||
String linePart2 = name + "</a>";
|
||||
String part2Format = String.format(linePart2Str, linePart2);
|
||||
sb.append(part2Format);
|
||||
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
String modifyStr = simpleDateFormat.format(new Date(f.lastModified()));
|
||||
|
||||
sb.append(modifyStr);
|
||||
sb.append(" - ").append("\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
for (File f : listFiles) {
|
||||
if (f.isFile()) {
|
||||
String name = f.getName();
|
||||
String part1Format = String.format(linePart1Str, name, name, name);
|
||||
sb.append(part1Format);
|
||||
|
||||
String linePart2 = name + "</a>";
|
||||
String part2Format = String.format(linePart2Str, linePart2);
|
||||
sb.append(part2Format);
|
||||
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||||
String modifyStr = simpleDateFormat.format(new Date(f.lastModified()));
|
||||
sb.append(modifyStr);
|
||||
|
||||
String sizeStr = String.format("%10d ", f.length());
|
||||
sb.append(sizeStr).append("\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sb.append(pageFooter);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static DefaultFullHttpResponse view(File dir, String path, HttpVersion version) throws IOException {
|
||||
if (path.startsWith("/")) {
|
||||
path = path.substring(1, path.length());
|
||||
}
|
||||
File file = new File(path);
|
||||
|
||||
if (isSubFile(dir, file)) {
|
||||
DefaultFullHttpResponse fullResp = new DefaultFullHttpResponse(version, HttpResponseStatus.OK);
|
||||
|
||||
if (file.isDirectory()) {
|
||||
if (!path.endsWith("/")) {
|
||||
fullResp.setStatus(HttpResponseStatus.FOUND).headers().set(HttpHeaderNames.LOCATION, "/" + path + "/");
|
||||
}
|
||||
|
||||
String renderResult = renderDir(file);
|
||||
fullResp.content().writeBytes(renderResult.getBytes("utf-8"));
|
||||
fullResp.headers().set(HttpHeaderNames.CONTENT_TYPE, "content-type: text/html; charset=utf-8");
|
||||
} else {
|
||||
FileInputStream fileInputStream = new FileInputStream(file);
|
||||
try {
|
||||
byte[] content = IOUtils.getBytes(fileInputStream);
|
||||
fullResp.content().writeBytes(content);
|
||||
HttpUtil.setContentLength(fullResp, fullResp.content().readableBytes());
|
||||
} finally {
|
||||
IOUtils.close(fileInputStream);
|
||||
}
|
||||
}
|
||||
return fullResp;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isSubFile(File parent, File child) throws IOException {
|
||||
String parentPath = parent.getCanonicalPath();
|
||||
String childPath = child.getCanonicalPath();
|
||||
if (parentPath.equals(childPath) || childPath.startsWith(parent.getCanonicalPath() + File.separator)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
package com.taobao.arthas.core.shell.term.impl.httptelnet;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
|
||||
import com.taobao.arthas.common.IOUtils;
|
||||
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.DefaultHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpResponse;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
import io.netty.handler.codec.http.HttpUtil;
|
||||
import io.netty.handler.codec.http.HttpVersion;
|
||||
import io.netty.handler.codec.http.LastHttpContent;
|
||||
import io.termd.core.http.HttpTtyConnection;
|
||||
import io.termd.core.util.Logging;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
|
||||
* @author hengyunabc 2019-11-06
|
||||
*/
|
||||
public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
|
||||
|
||||
private final String wsUri;
|
||||
|
||||
private File dir;
|
||||
|
||||
public HttpRequestHandler(String wsUri, File dir) {
|
||||
this.wsUri = wsUri;
|
||||
this.dir = dir;
|
||||
dir.mkdirs();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
|
||||
if (wsUri.equalsIgnoreCase(request.uri())) {
|
||||
ctx.fireChannelRead(request.retain());
|
||||
} else {
|
||||
if (HttpUtil.is100ContinueExpected(request)) {
|
||||
send100Continue(ctx);
|
||||
}
|
||||
|
||||
HttpResponse response = new DefaultHttpResponse(request.protocolVersion(),
|
||||
HttpResponseStatus.INTERNAL_SERVER_ERROR);
|
||||
|
||||
String path = new URI(request.uri()).getPath();
|
||||
|
||||
if ("/".equals(path)) {
|
||||
path = "/index.html";
|
||||
}
|
||||
|
||||
InputStream in = null;
|
||||
try {
|
||||
|
||||
DefaultFullHttpResponse fileViewResult = DirectoryBrowser.view(dir, path, request.protocolVersion());
|
||||
|
||||
if (fileViewResult != null) {
|
||||
response = fileViewResult;
|
||||
} else {
|
||||
URL res = HttpTtyConnection.class.getResource("/io/termd/core/http" + path);
|
||||
if (res != null) {
|
||||
DefaultFullHttpResponse fullResp = new DefaultFullHttpResponse(request.protocolVersion(),
|
||||
HttpResponseStatus.OK);
|
||||
in = res.openStream();
|
||||
byte[] tmp = new byte[256];
|
||||
for (int l = 0; l != -1; l = in.read(tmp)) {
|
||||
fullResp.content().writeBytes(tmp, 0, l);
|
||||
}
|
||||
int li = path.lastIndexOf('.');
|
||||
if (li != -1 && li != path.length() - 1) {
|
||||
String ext = path.substring(li + 1, path.length());
|
||||
String contentType;
|
||||
if ("html".equals(ext)) {
|
||||
contentType = "text/html";
|
||||
} else if ("js".equals(ext)) {
|
||||
contentType = "application/javascript";
|
||||
} else if ("css".equals(ext)) {
|
||||
contentType = "text/css";
|
||||
} else {
|
||||
contentType = null;
|
||||
}
|
||||
|
||||
if (contentType != null) {
|
||||
fullResp.headers().set(HttpHeaderNames.CONTENT_TYPE, contentType);
|
||||
}
|
||||
}
|
||||
response = fullResp;
|
||||
} else {
|
||||
response.setStatus(HttpResponseStatus.NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
ctx.write(response);
|
||||
ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
|
||||
future.addListener(ChannelFutureListener.CLOSE);
|
||||
IOUtils.close(in);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void send100Continue(ChannelHandlerContext ctx) {
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
|
||||
ctx.writeAndFlush(response);
|
||||
}
|
||||
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
||||
Logging.logReportedIoError(cause);
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
+13
-9
@@ -1,5 +1,6 @@
|
||||
package com.taobao.arthas.core.shell.term.impl.httptelnet;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
@@ -14,7 +15,6 @@ import io.netty.handler.stream.ChunkedWriteHandler;
|
||||
import io.netty.util.concurrent.ScheduledFuture;
|
||||
import io.termd.core.function.Consumer;
|
||||
import io.termd.core.function.Supplier;
|
||||
import io.termd.core.http.netty.HttpRequestHandler;
|
||||
import io.termd.core.http.netty.TtyWebSocketFrameHandler;
|
||||
import io.termd.core.telnet.TelnetHandler;
|
||||
import io.termd.core.telnet.netty.TelnetChannelHandler;
|
||||
@@ -31,7 +31,7 @@ public class ProtocolDetectHandler extends ChannelInboundHandlerAdapter {
|
||||
private Consumer<TtyConnection> ttyConnectionFactory;
|
||||
|
||||
public ProtocolDetectHandler(ChannelGroup channelGroup, final Supplier<TelnetHandler> handlerFactory,
|
||||
Consumer<TtyConnection> ttyConnectionFactory) {
|
||||
Consumer<TtyConnection> ttyConnectionFactory) {
|
||||
this.channelGroup = channelGroup;
|
||||
this.handlerFactory = handlerFactory;
|
||||
this.ttyConnectionFactory = ttyConnectionFactory;
|
||||
@@ -41,29 +41,32 @@ public class ProtocolDetectHandler extends ChannelInboundHandlerAdapter {
|
||||
|
||||
@Override
|
||||
public void channelActive(final ChannelHandlerContext ctx) throws Exception {
|
||||
detectTelnetFuture = ctx.executor().schedule(new Runnable() {
|
||||
detectTelnetFuture = ctx.channel().eventLoop().schedule(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
channelGroup.add(ctx.channel());
|
||||
TelnetChannelHandler handler = new TelnetChannelHandler(handlerFactory);
|
||||
ChannelPipeline pipeline = ctx.pipeline();
|
||||
pipeline.addLast(handler);
|
||||
ctx.fireChannelActive();
|
||||
pipeline.remove(ProtocolDetectHandler.this);
|
||||
ctx.fireChannelActive(); // trigger TelnetChannelHandler init
|
||||
}
|
||||
|
||||
}, 500, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) {
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
ByteBuf in = (ByteBuf) msg;
|
||||
if (in.readableBytes() < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
detectTelnetFuture.cancel(false);
|
||||
if (detectTelnetFuture != null && detectTelnetFuture.isCancellable()) {
|
||||
detectTelnetFuture.cancel(false);
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[4];
|
||||
byte[] bytes = new byte[3];
|
||||
in.getBytes(0, bytes);
|
||||
String httpHeader = new String(bytes);
|
||||
|
||||
@@ -77,11 +80,12 @@ public class ProtocolDetectHandler extends ChannelInboundHandlerAdapter {
|
||||
pipeline.addLast(new HttpServerCodec());
|
||||
pipeline.addLast(new ChunkedWriteHandler());
|
||||
pipeline.addLast(new HttpObjectAggregator(64 * 1024));
|
||||
pipeline.addLast(new HttpRequestHandler("/ws"));
|
||||
pipeline.addLast(new HttpRequestHandler("/ws", new File("arthas-output")));
|
||||
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
|
||||
pipeline.addLast(new TtyWebSocketFrameHandler(channelGroup, ttyConnectionFactory));
|
||||
}
|
||||
ctx.fireChannelRead(msg);
|
||||
ctx.fireChannelRead(in);
|
||||
pipeline.remove(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user