package com.github.ltsopensource.cmd;
import com.github.ltsopensource.core.commons.utils.Assert;
import com.github.ltsopensource.core.commons.utils.DateUtils;
import com.github.ltsopensource.core.json.JSON;
import com.github.ltsopensource.core.logger.Logger;
import com.github.ltsopensource.core.logger.LoggerFactory;
import java.io.*;
import java.net.Socket;
import java.net.URLDecoder;
import java.util.*;
/**
* @author Robert HG (254963746@qq.com) on 2/17/16.
*/
public class HttpCmdExecutor implements Runnable {
private static final Logger LOGGER = LoggerFactory.getLogger(HttpCmdExecutor.class);
private HttpCmdContext context;
private Socket socket;
public HttpCmdExecutor(HttpCmdContext context, Socket socket) {
this.context = context;
this.socket = socket;
}
@Override
public void run() {
try {
// 解析请求
HttpCmdRequest request = parseRequest();
Assert.notNull(request, "Request Error");
Assert.hasText(request.getCommand(), "Command is blank");
Assert.hasText(request.getNodeIdentity(), "nodeIdentity is blank");
HttpCmdProc httpCmdProc = context.getCmdProcessor(request.getNodeIdentity(), request.getCommand());
Assert.notNull(httpCmdProc, "Can not find the command:[" + request.getCommand() + "]");
sendResponse(HTTP_OK, JSON.toJSONString(httpCmdProc.execute(request)));
} catch (HttpCMDErrorException ignored) {
// 忽略
} catch (IllegalArgumentException e) {
sendError(HTTP_BADREQUEST, JSON.toJSONString(HttpCmdResponse.newResponse(false, e.getMessage())), false);
} catch (Throwable t) {
LOGGER.error("Error When Execute Command", t);
sendError(HTTP_INTERNALERROR, JSON.toJSONString(HttpCmdResponse.newResponse(false, "Error:" + t.getMessage())), false);
}
}
private HttpCmdRequest parseRequest() throws Exception {
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
StringTokenizer st = new StringTokenizer(in.readLine());
if (!st.hasMoreTokens())
sendError(HTTP_BADREQUEST, "BAD REQUEST: Syntax error");
String method = st.nextToken();
if (!st.hasMoreTokens())
sendError(HTTP_BADREQUEST, "BAD REQUEST: Missing URI");
String uri = st.nextToken();
Properties params = new Properties();
assert uri != null;
int qmi = uri.indexOf('?');
if (qmi >= 0) {
decodeParams(uri.substring(qmi + 1), params);
uri = uri.substring(0, qmi);
}
Properties header = new Properties();
if (st.hasMoreTokens()) {
String line = in.readLine();
while (line.trim().length() > 0) {
int p = line.indexOf(':');
header.put(line.substring(0, p).trim().toLowerCase(), line.substring(p + 1).trim());
line = in.readLine();
}
}
if (method.equalsIgnoreCase("POST")) {
long size = 0x7FFFFFFFFFFFFFFFL;
String contentLength = header.getProperty("Content-Length");
if (contentLength == null) {
contentLength = header.getProperty("content-length");
}
if (contentLength != null) {
size = Integer.parseInt(contentLength);
}
String postLine = "";
char buf[] = new char[512];
int read = in.read(buf);
while (read >= 0 && size > 0 && !postLine.endsWith("\r\n")) {
size -= read;
postLine += String.valueOf(buf, 0, read);
if (size > 0)
read = in.read(buf);
}
postLine = postLine.trim();
decodeParams(postLine, params);
}
return resolveRequest(uri, params);
}
protected static HttpCmdRequest resolveRequest(String uri, Properties params) {
HttpCmdRequest request = new HttpCmdRequest();
String[] pathNode = uri.substring(1, uri.length()).split("/");
String nodeIdentity = pathNode[0];
String command = pathNode[1];
;
request.setCommand(command);
request.setNodeIdentity(nodeIdentity);
for (Map.Entry<Object, Object> entry : params.entrySet()) {
request.addParam(String.valueOf(entry.getKey()), String.valueOf(entry.getValue()));
}
return request;
}
public static final String HTTP_OK = "200 OK", HTTP_REDIRECT = "301 Moved Permanently",
HTTP_FORBIDDEN = "403 Forbidden", HTTP_NOTFOUND = "404 Not Found",
HTTP_BADREQUEST = "400 Bad Request", HTTP_INTERNALERROR = "500 Internal Server Error",
HTTP_NOTIMPLEMENTED = "501 Not Implemented";
public static final String MIME_PLAINTEXT = "text/plain", MIME_HTML = "text/html",
MIME_DEFAULT_BINARY = "application/octet-stream";
private void sendError(String status, String msg) {
sendError(status, msg, true);
}
private void sendError(String status, String msg, boolean needInterrupt) {
sendResponse(status, msg);
if (needInterrupt) {
throw new HttpCMDErrorException();
}
}
private void sendResponse(String status, String msg) {
sendResponse(status, MIME_PLAINTEXT, null, new ByteArrayInputStream(msg.getBytes()));
}
private void decodeParams(String params, Properties p) throws Exception {
if (params == null)
return;
StringTokenizer st = new StringTokenizer(params, "&");
while (st.hasMoreTokens()) {
String e = st.nextToken();
int sep = e.indexOf('=');
if (sep >= 0) {
String key = e.substring(0, sep);
String value = URLDecoder.decode((e.substring(sep + 1)), "UTF-8");
p.put(key, value);
}
}
}
private void sendResponse(String status, String mime, Properties header, InputStream data) {
try {
if (status == null)
throw new Error("sendResponse(): Status can't be null.");
OutputStream out = socket.getOutputStream();
PrintWriter pw = new PrintWriter(out);
pw.print("HTTP/1.0 " + status + " \r\n");
if (mime != null)
pw.print("Content-Type: " + mime + "\r\n");
if (header == null || header.getProperty("Date") == null)
pw.print("Date: " + DateUtils.formatYMD_HMS(new Date()) + "\r\n");
if (header != null) {
Enumeration e = header.keys();
while (e.hasMoreElements()) {
String key = (String) e.nextElement();
String value = header.getProperty(key);
pw.print(key + ": " + value + "\r\n");
}
}
pw.print("\r\n");
pw.flush();
if (data != null) {
byte[] buff = new byte[2048];
while (true) {
int read = data.read(buff, 0, 2048);
if (read <= 0)
break;
out.write(buff, 0, read);
}
}
out.flush();
out.close();
if (data != null)
data.close();
} catch (IOException ioe) {
try {
socket.close();
} catch (Throwable ignored) {
}
}
}
private class HttpCMDErrorException extends RuntimeException {
public HttpCMDErrorException() {
super();
}
}
}