add http api handler

This commit is contained in:
kylixs
2020-03-23 09:55:55 +08:00
parent b6d55d0b0b
commit cfddec4bed
8 changed files with 319 additions and 14 deletions
@@ -0,0 +1,9 @@
package com.taobao.arthas.core.shell.session;
/**
* Arthas Session Manager
* @author gongdewei 2020-03-20
*/
public interface SessionManager {
}
@@ -0,0 +1,9 @@
package com.taobao.arthas.core.shell.session.impl;
/**
* Arthas Session Manager
* @author gongdewei 2020-03-20
*/
public interface SessionManagerImpl {
}
@@ -7,6 +7,7 @@ import java.net.URI;
import java.net.URL;
import com.taobao.arthas.common.IOUtils;
import com.taobao.arthas.core.shell.term.impl.http.api.HttpApiHandler;
import com.taobao.arthas.core.util.LogUtil;
import com.taobao.middleware.logger.Logger;
@@ -30,6 +31,7 @@ import io.termd.core.util.Logging;
/**
* @author <a href="mailto:julien@julienviet.com">Julien Viet</a>
* @author hengyunabc 2019-11-06
* @author gongdewei 2020-03-18
*/
public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
private static final Logger logger = LogUtil.getArthasLogger();
@@ -38,10 +40,13 @@ public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequ
private File dir;
private HttpApiHandler httpApiHandler;
public HttpRequestHandler(String wsUri, File dir) {
this.wsUri = wsUri;
this.dir = dir;
dir.mkdirs();
this.httpApiHandler = new HttpApiHandler();
}
@Override
@@ -53,30 +58,39 @@ public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequ
send100Continue(ctx);
}
HttpResponse response = new DefaultHttpResponse(request.protocolVersion(),
HttpResponseStatus.INTERNAL_SERVER_ERROR);
HttpResponse response = null;
String path = new URI(request.uri()).getPath();
if ("/".equals(path)) {
path = "/index.html";
}
try {
DefaultFullHttpResponse fileViewResult = DirectoryBrowser.view(dir, path, request.protocolVersion());
if (fileViewResult != null) {
response = fileViewResult;
} else {
FullHttpResponse fullResp = readFileFromResource(request, path);
if(fullResp != null){
response = fullResp;
} else {
response.setStatus(HttpResponseStatus.NOT_FOUND);
}
//handle http restful api
if ("/api".equals(path)) {
response = httpApiHandler.handle(request);
}
//try classpath resource first
if (response == null){
response = readFileFromResource(request, path);
}
//try output dir later, avoid overlay classpath resources files
if (response == null){
response = DirectoryBrowser.view(dir, path, request.protocolVersion());
}
//not found
if (response == null){
response = createResponse(request, HttpResponseStatus.NOT_FOUND);
}
} catch (Throwable e) {
logger.error("arthas", "arthas process http request error: " + request.uri(), e);
} finally {
//If it is null, an error may occur
if (response == null){
response = createResponse(request, HttpResponseStatus.INTERNAL_SERVER_ERROR);
}
ctx.write(response);
ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
future.addListener(ChannelFutureListener.CLOSE);
@@ -84,6 +98,10 @@ public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequ
}
}
private DefaultHttpResponse createResponse(FullHttpRequest request, HttpResponseStatus status) {
return new DefaultHttpResponse(request.protocolVersion(), status);
}
private FullHttpResponse readFileFromResource(FullHttpRequest request, String path) throws IOException {
DefaultFullHttpResponse fullResp = null;
InputStream in = null;
@@ -0,0 +1,16 @@
package com.taobao.arthas.core.shell.term.impl.http.api;
/**
* Http Api exception
* @author gongdewei 2020-03-19
*/
public class ApiException extends Exception {
public ApiException(String message) {
super(message);
}
public ApiException(String message, Throwable cause) {
super(message, cause);
}
}
@@ -0,0 +1,56 @@
package com.taobao.arthas.core.shell.term.impl.http.api;
import java.util.Map;
/**
* Http Api exception
* @author gongdewei 2020-03-19
*/
public class ApiRequest {
private String action;
private boolean sync;
private String command;
private Map<String, Object> options;
@Override
public String toString() {
return "ApiRequest{" +
"action='" + action + '\'' +
", sync=" + sync +
", command='" + command + '\'' +
", options=" + options +
'}';
}
public String getAction() {
return action;
}
public void setAction(String action) {
this.action = action;
}
public boolean isSync() {
return sync;
}
public void setSync(boolean sync) {
this.sync = sync;
}
public String getCommand() {
return command;
}
public void setCommand(String command) {
this.command = command;
}
public Map<String, Object> getOptions() {
return options;
}
public void setOptions(Map<String, Object> options) {
this.options = options;
}
}
@@ -0,0 +1,72 @@
package com.taobao.arthas.core.shell.term.impl.http.api;
/**
* Http Api exception
* @author gongdewei 2020-03-19
*/
public class ApiResponse<T> {
private String requestId;
private ApiState state;
private String message;
private String sessionId;
private String consumerId;
private String jobId;
private T body;
public String getRequestId() {
return requestId;
}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
public ApiState getState() {
return state;
}
public void setState(ApiState state) {
this.state = state;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getSessionId() {
return sessionId;
}
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
public String getConsumerId() {
return consumerId;
}
public void setConsumerId(String consumerId) {
this.consumerId = consumerId;
}
public String getJobId() {
return jobId;
}
public void setJobId(String jobId) {
this.jobId = jobId;
}
public T getBody() {
return body;
}
public void setBody(T body) {
this.body = body;
}
}
@@ -0,0 +1,14 @@
package com.taobao.arthas.core.shell.term.impl.http.api;
/**
* Http API response state
* @author gongdewei 2020-03-19
*/
public enum ApiState {
/** accepted */
SCHEDULED,
RUNNING,
SUCCEEDED,
FAILED,
REFUSED
}
@@ -0,0 +1,111 @@
package com.taobao.arthas.core.shell.term.impl.http.api;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.taobao.arthas.core.server.ArthasBootstrap;
import com.taobao.arthas.core.shell.ShellServer;
import com.taobao.arthas.core.util.LogUtil;
import com.taobao.arthas.core.util.StringUtils;
import com.taobao.middleware.logger.Logger;
import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.http.*;
import io.netty.util.CharsetUtil;
/**
* Http Restful Api Handler
* @author gongdewei 2020-03-18
*/
public class HttpApiHandler {
private static final Logger logger = LogUtil.getArthasLogger();
public HttpResponse handle(FullHttpRequest request) throws Exception {
DefaultFullHttpResponse response = new DefaultFullHttpResponse(request.protocolVersion(),
HttpResponseStatus.OK);
ApiResponse result = null;
String requestBody = null;
try {
HttpMethod method = request.method();
if (HttpMethod.POST.equals(method)){
requestBody = getBody(request);
ApiRequest apiRequest = parseRequest(requestBody);
result = processRequest(apiRequest);
} else {
result = createResponse(ApiState.REFUSED, "Unsupported http method: "+method.name());
}
} catch (Throwable e) {
result = createResponse(ApiState.FAILED, "Process request error: "+e.getMessage());
logger.error("arthas", "arthas process http api request error: " + request.uri()+", request body: "+requestBody, e);
}
if (result == null) {
result = createResponse(ApiState.FAILED, "The request was not processed");
}
String jsonResult = JSON.toJSONString(result);
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=utf-8");
response.content().writeBytes(jsonResult.getBytes("UTF-8"));
return response;
}
private ApiRequest parseRequest(String requestBody) throws ApiException {
if (StringUtils.isBlank(requestBody)){
throw new ApiException("parse request failed: request body is empty");
}
try {
//Object jsonRequest = JSON.parse(requestBody);
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(requestBody, ApiRequest.class);
} catch (Exception e) {
throw new ApiException("parse request failed: "+e.getMessage(), e);
}
}
private ApiResponse processRequest(ApiRequest apiRequest) {
String action = apiRequest.getAction();
if ("exec".equalsIgnoreCase(action)){
return processExecRequest(apiRequest);
} else if("init_session".equalsIgnoreCase(action)){
return processInitSessionRequest(apiRequest);
} else if("close_session".equalsIgnoreCase(action)){
return processCloseSessionRequest(apiRequest);
}
return createResponse(ApiState.REFUSED, "Unsupported action: "+action);
}
private ApiResponse processInitSessionRequest(ApiRequest apiRequest) {
// ShellServer shellServer = getShellServer();
// shellServer.createShell()
return null;
}
private ApiResponse processCloseSessionRequest(ApiRequest apiRequest) {
return null;
}
private ApiResponse processExecRequest(ApiRequest apiRequest) {
String command = apiRequest.getCommand();
//TODO
return null;
}
private ApiResponse createResponse(ApiState apiState, String message) {
ApiResponse apiResponse = new ApiResponse();
apiResponse.setState(apiState);
apiResponse.setMessage(message);
return apiResponse;
}
private String getBody(FullHttpRequest request){
ByteBuf buf = request.content();
return buf.toString(CharsetUtil.UTF_8);
}
// private ShellServer getShellServer() {
// return ArthasBootstrap.getInstance().getShellServer();
// }
}