content: /**
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  package com.alibaba.jstorm.daemon.supervisor;
  
  import backtype.storm.Constants;
  import backtype.storm.daemon.Shutdownable;
  import backtype.storm.utils.Utils;
  import com.alibaba.jstorm.client.ConfigExtension;
  import com.alibaba.jstorm.daemon.worker.Worker;
  import com.alibaba.jstorm.utils.*;
  import com.google.common.annotations.VisibleForTesting;
  import com.google.common.base.Joiner;
  import com.google.common.collect.Maps;
  import com.sun.net.httpserver.HttpExchange;
  import com.sun.net.httpserver.HttpHandler;
  import com.sun.net.httpserver.HttpServer;
  import org.apache.commons.io.IOUtils;
  import org.apache.commons.lang.StringUtils;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import java.io.*;
  import java.net.*;
  import java.nio.MappedByteBuffer;
  import java.nio.channels.FileChannel;
  import java.util.*;
  import java.util.concurrent.Executor;
  import java.util.concurrent.Executors;
  
  /**
   * @author Johnfang (xiaojian.fxj@alibaba-inc.com)
   */
  public class Httpserver implements Shutdownable {
      private static final Logger LOG = LoggerFactory.getLogger(Httpserver.class);
  
      private HttpServer hs;
      private int port;
      private Map conf;
  
      public Httpserver(int port, Map conf) {
          this.port = port;
          this.conf = conf;
      }
  
      static class LogHandler implements HttpHandler {
          private String logDir;
          private String stormHome;
          private ArrayList<String> accessDirs = new ArrayList<>();
          Map conf;
          private final int pageSize;
          private boolean debug = false;
  
          public LogHandler(Map conf) {
              this.pageSize = ConfigExtension.getLogPageSize(conf);
              logDir = JStormUtils.getLogDir();
              String logDirPath = PathUtils.getCanonicalPath(logDir);
              if (logDirPath == null) {
                  accessDirs.add(logDir);
              } else {
                  accessDirs.add(logDirPath);
              }
  
              stormHome = System.getProperty("jstorm.home");
              if (stormHome != null) {
                  String stormHomePath = PathUtils.getCanonicalPath(stormHome);
                  if (stormHomePath == null) {
                      accessDirs.add(stormHome);
                  } else {
                      accessDirs.add(stormHomePath);
                  }
              }
  
              String confDir = System.getProperty(Constants.JSTORM_CONF_DIR);
              if (!StringUtils.isBlank(confDir)) {
                  String confDirPath = PathUtils.getCanonicalPath(confDir);
                  if (confDirPath != null) {
                      accessDirs.add(confDirPath);
                  }
              }
  
              this.conf = conf;
              LOG.info("logview log dir=" + logDir);
          }
  
          @VisibleForTesting
          public void setDebug(boolean debug) {
              this.debug = debug;
          }
  
          @VisibleForTesting
          public void setLogDir(String dir) {
              this.logDir = dir;
          }
  
          public void handlFailure(HttpExchange t, String errorMsg) throws IOException {
              LOG.error(errorMsg);
  
              byte[] data = errorMsg.getBytes();
              sendResponse(t, HttpURLConnection.HTTP_BAD_REQUEST, data);
          }
  
          public void handle(HttpExchange t) throws IOException {
              URI uri = t.getRequestURI();
              Map<String, String> paramMap = parseRawQuery(uri.getRawQuery());
              LOG.info("Received command " + paramMap);
  
              String cmd = paramMap.get(HttpserverUtils.HTTPSERVER_LOGVIEW_PARAM_CMD);
              if (StringUtils.isBlank(cmd)) {
                  handlFailure(t, "Bad Request, please specify command!");
                  return;
              }
  
              if (HttpserverUtils.HTTPSERVER_LOGVIEW_PARAM_CMD_SHOW.equals(cmd)) {
                  handleShowLog(t, paramMap);
              } else if (HttpserverUtils.HTTPSERVER_LOGVIEW_PARAM_CMD_LIST.equals(cmd)) {
                  handleListDir(t, paramMap);
              } else if (HttpserverUtils.HTTPSERVER_LOGVIEW_PARAM_CMD_JSTACK.equals(cmd)) {
                  handleJstack(t, paramMap);
              } else if (HttpserverUtils.HTTPSERVER_LOGVIEW_PARAM_CMD_JSTAT.equals(cmd)) {
                  handleJstat(t, paramMap);
              } else if (HttpserverUtils.HTTPSERVER_LOGVIEW_PARAM_CMD_SHOW_CONF.equals(cmd)) {
                  handleShowConf(t, paramMap);
              } else if (HttpserverUtils.HTTPSERVER_LOGVIEW_PARAM_CMD_SEARCH_LOG.equals(cmd)) {
                  handleSearchLog(t, paramMap);
              } else if (HttpserverUtils.HTTPSERVER_LOGVIEW_PARAM_CMD_DOWNLOAD.equals(cmd)) {
                  handleDownloadLog(t, paramMap);
              } else {
                  handlFailure(t, "Bad Request, unsupported command " + cmd);
              }
          }
  
          private void accessCheck(String fileName) throws IOException {
              if (debug) {
                  return;
              }
  
              File file = new File(fileName);
              String canonicalPath = file.getCanonicalPath();
  
              boolean isChild = false;
              for (String dir : accessDirs) {
                  if (canonicalPath.contains(dir)) {
                      isChild = true;
                      break;
                  }
              }
  
              if (!isChild) {
                  LOG.error("Access one disallowed path: " + canonicalPath);
                  throw new IOException("Destination file/path is not accessible.");
              }
          }
  
          private Map<String, String> parseRawQuery(String uriRawQuery) {
              Map<String, String> paramMap = Maps.newHashMap();
  
              for (String param : StringUtils.split(uriRawQuery, "&")) {
                  String[] pair = StringUtils.split(param, "=");
                  if (pair.length == 2) {
                      paramMap.put(pair[0], pair[1]);
                  }
              }
  
              return paramMap;
          }
  
          private void handleShowLog(HttpExchange t, Map<String, String> paramMap) throws IOException {
              Pair<Long, byte[]> logPair = queryLog(t, paramMap);
              if (logPair == null) {
                  return;
              }
  
              String size = String.format(HttpserverUtils.HTTPSERVER_LOGVIEW_PARAM_SIZE_FORMAT, logPair.getFirst());
              byte[] sizeBytes = size.getBytes();
  
              byte[] logData = logPair.getSecond();
  
              t.sendResponseHeaders(HttpURLConnection.HTTP_OK, sizeBytes.length + logData.length);
              OutputStream os = t.getResponseBody();
              os.write(sizeBytes);
              os.write(logData);
              os.close();
          }
  
          private void handleDownloadLog(HttpExchange t, Map<String, String> paramMap) throws IOException {
              Pair<Long, byte[]> logPair = queryLog(t, paramMap);
              sendResponse(t, HttpURLConnection.HTTP_OK, logPair.getSecond());
          }
  
  
          private Pair<Long, byte[]> queryLog(HttpExchange t, Map<String, String> paramMap) throws IOException {
              String fileParam = paramMap.get(HttpserverUtils.HTTPSERVER_LOGVIEW_PARAM_LOGFILE);
              String _pageSize = paramMap.get(HttpserverUtils.HTTPSERVER_LOGVIEW_PAGE_SIZE);
              if (StringUtils.isBlank(fileParam)) {
                  handlFailure(t, "Bad Request, Params Error, no log file name.");
                  return null;
              }
              int pageSize = this.pageSize;
              if (!StringUtils.isBlank(_pageSize)) {
                  pageSize = JStormUtils.parseInt(_pageSize, this.pageSize);
              }
              String logFile = Joiner.on(File.separator).join(logDir, fileParam);
              accessCheck(logFile);
              FileChannel fc = null;
              MappedByteBuffer fout;
              long fileSize;
              byte[] ret;
              try {
                  fc = new RandomAccessFile(logFile, "r").getChannel();
                  fileSize = fc.size();
                  long position = fileSize - pageSize;
                  try {
                      String posStr = paramMap.get(HttpserverUtils.HTTPSERVER_LOGVIEW_PARAM_POS);
                      if (!StringUtils.isBlank(posStr)) {
                          position = Long.valueOf(posStr);
                      }
                  } catch (Exception e) {
                      LOG.warn("Invalid position " + position);
                  }
                  if (position < 0) {
                      position = 0L;
                  }
  
                  long size = Math.min(fileSize - position, pageSize);
  
                  LOG.info("logview " + logFile + ", position=" + position + ", size=" + size);
                  fout = fc.map(FileChannel.MapMode.READ_ONLY, position, size);
  
                  ret = new byte[(int) size];
                  fout.get(ret);
                  String str = new String(ret, ConfigExtension.getLogViewEncoding(conf));
                  return new Pair<>(fileSize, str.getBytes());
  
              } catch (FileNotFoundException e) {
                  LOG.warn(e.getMessage(), e);
                  handlFailure(t, "Bad Request, Failed to find " + fileParam);
                  return null;
  
              } catch (IOException e) {
                  LOG.warn(e.getMessage(), e);
                  handlFailure(t, "Bad Request, Failed to open " + fileParam);
                  return null;
              } finally {
                  if (fc != null) {
                      IOUtils.closeQuietly(fc);
                  }
              }
  
          }
  
          byte[] getJSonFiles(String dir) throws Exception {
              Map<String, FileAttribute> fileMap = new HashMap<>();
  
              String path = logDir;
              if (dir != null) {
                  path = path + File.separator + dir;
              }
              accessCheck(path);
              LOG.info("List dir " + path);
  
              File file = new File(path);
              String[] files = file.list();
              if (files == null) {
                  files = new String[]{};
              }
  
              for (String fileName : files) {
                  String logFile = Joiner.on(File.separator).join(path, fileName);
                  FileAttribute fileAttribute = new FileAttribute();
                  fileAttribute.setFileName(fileName);
  
                  File subFile = new File(logFile);
                  Date modify = new Date(subFile.lastModified());
                  fileAttribute.setModifyTime(TimeFormat.getSecond(modify));
                  if (subFile.isFile()) {
                      fileAttribute.setIsDir(String.valueOf(false));
                      fileAttribute.setSize(String.valueOf(subFile.length()));
  
                      fileMap.put(logFile, fileAttribute);
                  } else if (subFile.isDirectory()) {
                      fileAttribute.setIsDir(String.valueOf(true));
                      fileAttribute.setSize(String.valueOf(4096));
  
                      fileMap.put(logFile, fileAttribute);
                  }
              }
              String fileJsonStr = JStormUtils.to_json(fileMap);
              return fileJsonStr.getBytes();
          }
  
          void handleListDir(HttpExchange t, Map<String, String> paramMap) throws IOException {
              byte[] filesJson;
  
              try {
                  String dir = paramMap.get(HttpserverUtils.HTTPSERVER_LOGVIEW_PARAM_DIR);
                  filesJson = getJSonFiles(dir);
              } catch (Exception e) {
                  LOG.error("Failed to list files", e);
                  handlFailure(t, "Failed to get file list");
                  return;
              }
  
              sendResponse(t, HttpURLConnection.HTTP_OK, filesJson);
          }
  
          /**
           * log search, migrated from STORM-902
           *
           * the following params are needed:
           * - file *
           * - offset *
           * - max_match
           * - look_back: num of context lines backward from match
           * - look_ahead: num of context lines forward from match
           * - key:* search keyword
           * - case_ignore: true/false
           * - search_from: search direction, head: from begin to end, tail: from end to begin, default is tail
           *
           * following values are returned:
           * - error: true/false
           * - msg: the error msg if any
           * - num_match: the match num
           * - next_offset
           * - match_results: map<offset, match_result>  note that no consecutive match results are overlapped.
           */
          void handleSearchLog(HttpExchange t, Map<String, String> paramMap) throws IOException {
              int maxMatch = JStormUtils.parseInt(paramMap.get("max_match"), ConfigExtension.getMaxMatchPerLogSearch(conf));
              long offset = JStormUtils.parseLong(paramMap.get("offset"), 0);
              String logFile = paramMap.get("file");
              int lookBack = JStormUtils.parseInt(paramMap.get("look_back"), 2);
              int lookAhead = JStormUtils.parseInt(paramMap.get("look_ahead"), 10);
              boolean caseIgnore = JStormUtils.parseBoolean(paramMap.get("case_ignore"), false);
              int maxBlocks = ConfigExtension.getMaxBlocksPerLogSearch(conf);
              int blockSize = HttpserverUtils.LOG_SEARCH_BLOCK_SIZE;
              String key = paramMap.get("key");
              if (caseIgnore) {
                  key = key.toLowerCase();
              }
              key = URLDecoder.decode(key, "UTF-8");
  
              Map<Object, Object> ret = new HashMap<>();
              if (StringUtils.isBlank(key)) {
                  error(ret, "search key cannot be empty!");
  
                  String resp = JStormUtils.to_json(ret);
                  sendResponse(t, HttpURLConnection.HTTP_OK, resp);
                  return;
              }
  
              if (debug) {
                  System.out.println("Search for key:" + key);
                  System.out.println("===================================\n");
              }
  
              logFile = Joiner.on(File.separator).join(logDir, logFile);
              accessCheck(logFile);
  
              //search
              String searchFrom = paramMap.get("search_from");
              if (searchFrom != null && searchFrom.equals("head")) {
                  ret = searchFromHead(logFile, offset, key, maxMatch, lookBack, lookAhead, maxBlocks, blockSize, caseIgnore);
              } else {
                  ret = searchFromTail(logFile, offset, key, maxMatch, lookBack, lookAhead, maxBlocks, blockSize, caseIgnore);
              }
  
              String resp = JStormUtils.to_json(ret);
              sendResponse(t, HttpURLConnection.HTTP_OK, resp);
          }
  
  
          private Map<Object, Object> searchFromTail(String logFile, long offset, String key, int maxMatch, int lookBack,
                                                     int lookAhead, int maxBlocks, int blockSize, boolean caseIgnore) throws IOException {
              Map<Object, Object> ret = new HashMap<>();
              Map<Long, String> matchResults = new HashMap<>();   //<offset, match content>
              int match = 0;
  
              String encoding = ConfigExtension.getLogViewEncoding(conf);
              FileChannel fc = null;
              MappedByteBuffer fout;
              final long fileSize;
              try {
                  RandomAccessFile randomAccess = new RandomAccessFile(logFile, "r");
                  fc = randomAccess.getChannel();
                  fileSize = fc.size();
  
                  if (offset == 0 || offset < fileSize) {
                      // if offset is 0, we search from the end.
                      long pos = fileSize;
                      // user have not specify search start position, we start from end default
                      if (offset > 0) {
                          pos = offset;
                      }
                      long matchOffset = pos;      // the offset of match result
                      int jumpLines = 0;
                      StringBuilder matchContent = new StringBuilder();
  
                      for (int i = 0; i < maxBlocks && pos > 0 && match < maxMatch; i++) {
                          long bufferSize = blockSize;
                          if (pos < blockSize) {
                              // if current pos is less than block size , we start search from begin
                              bufferSize = pos;
                              pos = 0;
                          } else {
                              pos = pos - bufferSize;
                          }
                          fout = fc.map(FileChannel.MapMode.READ_ONLY, pos, bufferSize);
                          byte[] buffer = new byte[(int) bufferSize];
                          fout.get(buffer);
                          String data = new String(buffer, encoding);
                          String[] lines = data.split("\\r\\n|\\n|\\r");
  
                          int[] line2pos = new int[lines.length];     //the position of the end of the line
                          for (int j = 0; j < lines.length; j++) {
                              line2pos[j] = lines[j].getBytes(encoding).length;
                              if (j > 0) {
                                  line2pos[j] += line2pos[j - 1];
                              }
                          }
  
                          // begin to search this block
                          for (int j = 0; j < lines.length; ) {
                              String line = lines[j];
                              boolean isMatch = caseIgnore ? line.toLowerCase().contains(key) : line.contains(key);
                              if (isMatch) {
                                  int start = Math.max(0, j - lookBack);
                                  j += lookAhead;
                                  matchOffset = start > 0 ? pos + line2pos[start - 1] : pos;
                                  // jumps out of current block
                                  if (j >= lines.length) {
                                      jumpLines = j - lines.length + 1;
                                      j = lines.length - 1;
                                  }
                                  match++;
  
                                  // make partial search result, if there's jumpLines, the result is not complete.
                                  // will not append the last line
                                  for (int k = start; k < j; k++) {
                                      matchContent.append(lines[k]).append("\n");
                                  }
                                  // concat the jump out lines
                                  if (jumpLines > 0) {
                                      readJumpLines(randomAccess, pos + line2pos[j], jumpLines, matchContent);
                                      jumpLines = 0;
                                      j = lines.length;
                                  }
                                  // if it's the last match, we should ignore jumpLines
                                  matchResults.put(matchOffset, matchContent.toString());
                                  if (debug) {
                                      System.out.println("==== match " + match + " ==== offset: " + matchOffset);
                                      System.out.println(matchContent.toString());
                                      System.out.println();
                                  }
                                  matchContent = new StringBuilder();
  
                              } else {
                                  j++;
                              }
                          }
  
                          if (match < maxMatch && pos > 0) {
                              // move back from the line/word break
                              int lineBreakOffset = lines[0].getBytes(encoding).length;
                              if (lineBreakOffset < blockSize) {
                                  pos = pos + lineBreakOffset;
                              }
                          }
                      }
                      ret.put("num_match", match);
                      ret.put("match_results", matchResults);
                      ret.put("next_offset", pos);
                  } else {
                      error(ret, "pos exceeds file size!");
                  }
              } catch (FileNotFoundException e) {
                  LOG.warn("Error", e);
                  error(ret, "Bad Request, Failed to find " + logFile);
              } catch (IOException e) {
                  LOG.warn("Error", e);
                  error(ret, "Bad Request, Failed to open " + logFile);
              } finally {
                  if (fc != null) {
                      IOUtils.closeQuietly(fc);
                  }
              }
  
              return ret;
          }
  
          private void readJumpLines(RandomAccessFile randomAccessFile, long pos, int lines, StringBuilder matchContent) {
              try {
                  randomAccessFile.seek(pos);
                  while (lines-- > 0) {
                      String line = randomAccessFile.readLine();
                      if (line != null) {
                          matchContent.append(line).append("\n");
                      }
                  }
              } catch (IOException e) {
                  e.printStackTrace();
              }
          }
  
          private Map<Object, Object> searchFromHead(String logFile, long offset, String key, int maxMatch, int lookBack,
                                                     int lookAhead, int maxBlocks, int blockSize, boolean caseIgnore) throws IOException {
              Map<Object, Object> ret = new HashMap<>();
              Map<Long, String> matchResults = new HashMap<>();   //<offset, match content>
              int match = 0;
  
              String encoding = ConfigExtension.getLogViewEncoding(conf);
              FileChannel fc = null;
              MappedByteBuffer fout;
              long fileSize;
              try {
                  fc = new RandomAccessFile(logFile, "r").getChannel();
                  fileSize = fc.size();
  
                  if (offset < fileSize) {
                      long pos = offset;
                      long matchOffset = offset;
                      int jumpLines = 0;
                      StringBuilder matchContent = new StringBuilder();
  
                      for (int i = 0; i < maxBlocks && pos < fileSize && match < maxMatch; i++) {
                          long bufferSize = Math.min(fc.size() - pos, blockSize);
                          fout = fc.map(FileChannel.MapMode.READ_ONLY, pos, bufferSize);
                          byte[] buffer = new byte[(int) bufferSize];
                          fout.get(buffer);
                          String data = new String(buffer, encoding);
                          String[] lines = data.split("\\r\\n|\\n|\\r");
  
                          int[] line2pos = new int[lines.length];
                          for (int j = 0; j < lines.length; j++) {
                              line2pos[j] = lines[j].getBytes(encoding).length;
                              if (j > 0) {
                                  line2pos[j] += line2pos[j - 1];
                              }
                          }
  
                          for (int j = 0; j < lines.length && match < maxMatch; ) {
                              // rotate lines from last match
                              if (jumpLines > 0) {
                                  for (int m = 0; m < jumpLines && m < lines.length; m++) {
                                      matchContent.append(lines[m]).append("\n");
                                  }
                                  j = jumpLines;
                                  jumpLines = 0;
  
  //                                matchResults.add(matchContent.toString());
                                  matchResults.put(matchOffset, matchContent.toString());
                                  if (debug) {
  //                                    System.out.println("==== match " + match + " ====");
                                      System.out.println("==== match " + match + " ==== offset: " + matchOffset);
                                      System.out.println(matchContent.toString());
                                      System.out.println();
                                  }
                                  matchContent = new StringBuilder();
                                  continue;
                              }
  
                              String line = lines[j];
                              boolean isMatch = caseIgnore ? line.toLowerCase().contains(key) : line.contains(key);
                              if (isMatch) {
                                  int start = Math.max(0, j - lookBack);
                                  j += lookAhead;
                                  matchOffset = start > 0 ? pos + line2pos[start - 1] : pos;
                                  // jumps out of current block
                                  if (j >= lines.length) {
                                      jumpLines = j - lines.length + 1;
                                      j = lines.length - 1;
                                  }
                                  // search finishes
                                  if (++match >= maxMatch) {
                                      pos += line2pos[j];
                                  }
  
                                  // make partial search result, if there's jumpLines, the result is not complete.
                                  for (int k = start; k < j; k++) {
                                      matchContent.append(lines[k]).append("\n");
                                  }
                                  // if it's the last match, we should ignore jumpLines
                                  if (jumpLines == 0 || match >= maxMatch) {
  //                                    matchResults.add(matchContent.toString());
                                      matchResults.put(matchOffset, matchContent.toString());
                                      if (debug) {
                                          System.out.println("==== match " + match + " ==== offset: " + matchOffset);
                                          System.out.println(matchContent.toString());
                                          System.out.println();
                                      }
                                      matchContent = new StringBuilder();
                                  } else {
                                      j++;
                                  }
                              } else {
                                  j++;
                              }
                          }
  
                          if (match < maxMatch) {
                              // move back from the line/word break
                              int lineBreakOffset = lines[lines.length - 1].getBytes(encoding).length;
                              if (lineBreakOffset < blockSize) {
                                  pos = pos + blockSize - lineBreakOffset;
                              } else {
                                  pos += blockSize;
                              }
                          }
                      }
                      ret.put("num_match", match);
                      ret.put("match_results", matchResults);
                      ret.put("next_offset", pos);
                  } else {
                      error(ret, "pos exceeds file size!");
                  }
              } catch (FileNotFoundException e) {
                  LOG.warn("Error", e);
                  error(ret, "Bad Request, Failed to find " + logFile);
              } catch (IOException e) {
                  LOG.warn("Error", e);
                  error(ret, "Bad Request, Failed to open " + logFile);
              } finally {
                  if (fc != null) {
                      IOUtils.closeQuietly(fc);
                  }
              }
  
              return ret;
          }
  
          void error(Map<Object, Object> ret, String msg) {
              ret.put("error", true);
              ret.put("msg", msg);
          }
  
          void handleJstack(StringBuilder sb, Integer pid) {
              String cmd = "jstack " + pid;
              try {
                  LOG.info("Begin to execute " + cmd);
                  String output = JStormUtils.launchProcess(cmd, new HashMap<String, String>(), false);
                  sb.append(output);
                  LOG.info("Successfully get output of " + cmd);
              } catch (IOException e) {
                  LOG.info("Failed to execute " + cmd, e);
                  sb.append("Failed to execute " + cmd);
              } catch (Exception e) {
                  LOG.error(e.getMessage(), e);
                  sb.append("Failed to execute " + cmd + ", " + e.getCause());
              }
          }
  
  
          void handleJstat(StringBuilder sb, Integer pid) {
              String cmd = "jstat -gc " + pid;
              try {
                  LOG.info("Begin to execute " + cmd);
                  String output = JStormUtils.launchProcess(cmd, new HashMap<String, String>(), false);
                  sb.append(output);
  
                  LOG.info("Successfully get output of " + cmd);
              } catch (IOException e) {
                  LOG.info("Failed to execute " + cmd, e);
                  sb.append("Failed to execute " + cmd);
              } catch (Exception e) {
                  LOG.error(e.getMessage(), e);
                  sb.append("Failed to execute " + cmd + ", " + e.getCause());
              }
          }
  
          void handleJstack(HttpExchange t, Map<String, String> paramMap) throws IOException {
              String workerPort = paramMap.get(HttpserverUtils.HTTPSERVER_LOGVIEW_PARAM_WORKER_PORT);
              if (workerPort == null) {
                  handlFailure(t, "worker port is not set!");
                  return;
              }
  
              LOG.info("Begin to get jstack of " + workerPort);
              StringBuilder sb = new StringBuilder();
              List<Integer> pids = Worker.getOldPortPids(workerPort);
              for (Integer pid : pids) {
                  sb.append("!!!!!!!!!!!!!!!!!!\r\n");
                  sb.append("WorkerPort:" + workerPort + ", pid:" + pid);
                  sb.append("\r\n!!!!!!!!!!!!!!!!!!\r\n");
  
                  handleJstack(sb, pid);
              }
  
              byte[] data = sb.toString().getBytes();
              sendResponse(t, HttpURLConnection.HTTP_OK, data);
          }
  
          void handleJstat(HttpExchange t, Map<String, String> paramMap) throws IOException {
              String workerPort = paramMap.get(HttpserverUtils.HTTPSERVER_LOGVIEW_PARAM_WORKER_PORT);
              if (workerPort == null) {
                  handlFailure(t, "worker port is not set!");
                  return;
              }
  
              LOG.info("Begin to get jstat of " + workerPort);
              StringBuilder sb = new StringBuilder();
              List<Integer> pids = Worker.getOldPortPids(workerPort);
              for (Integer pid : pids) {
                  sb.append("!!!!!!!!!!!!!!!!!!\r\n");
                  sb.append("WorkerPort:" + workerPort + ", pid:" + pid);
                  sb.append("\r\n!!!!!!!!!!!!!!!!!!\r\n");
  
                  handleJstat(sb, pid);
              }
  
              byte[] data = sb.toString().getBytes();
              sendResponse(t, HttpURLConnection.HTTP_OK, data);
          }
  
          void handleShowConf(HttpExchange t, Map<String, String> paramMap) throws IOException {
              byte[] json;
              try {
                  String tmp = Utils.to_json(conf);
                  json = tmp.getBytes();
              } catch (Exception e) {
                  LOG.error("Failed to get configuration", e);
                  handlFailure(t, "Failed to get configuration");
                  return;
              }
  
              sendResponse(t, HttpURLConnection.HTTP_OK, json);
          }
  
          void sendResponse(HttpExchange t, int retCode, String data) throws IOException {
              if (debug) {
                  LOG.info("HTTP:{}, search result:{}", retCode, data);
              }
              byte[] bytes = data.getBytes();
              sendResponse(t, retCode, bytes);
          }
  
          void sendResponse(HttpExchange t, int retCode, byte[] data) throws IOException {
              if (t != null) {
                  t.sendResponseHeaders(retCode, data.length);
                  OutputStream os = t.getResponseBody();
                  os.write(data);
                  os.close();
              }
          }
      }// LogHandler
  
      public void start() {
          int numHandler = 3;
          InetSocketAddress socketAddr = new InetSocketAddress(port);
          Executor executor = Executors.newFixedThreadPool(numHandler);
          try {
              hs = HttpServer.create(socketAddr, 0);
              hs.createContext(HttpserverUtils.HTTPSERVER_CONTEXT_PATH_LOGVIEW, new LogHandler(conf));
              hs.setExecutor(executor);
              hs.start();
          } catch (BindException e) {
              LOG.info("HttpServer has started already!");
              hs = null;
              return;
          } catch (IOException e) {
              LOG.error("Failed to start HttpServer", e);
              hs = null;
              return;
          }
          LOG.info("Success started HttpServer at port:" + port);
  
      }
  
      @Override
      public void shutdown() {
          if (hs != null) {
              hs.stop(0);
              LOG.info("Successfully stopped http server");
          }
      }
  
      public static void main(String[] args) throws Exception {
          Map conf = new HashMap();
          LogHandler logHandler = new LogHandler(conf);
          logHandler.setDebug(true);
          logHandler.setLogDir("/Users/wuchong/Downloads/");
  
          Map<String, String> params = new HashMap<>();
  //        params.put("offset", "0");
  //        params.put("search_from", "head");
          params.put("file", "SequenceTest6-worker-6800.log");
          logHandler.handleSearchLog(null, params);
  
          params.put("key", "info");
          logHandler.handleSearchLog(null, params);
  
          params.put("case_ignore", "true");
          logHandler.handleSearchLog(null, params);
  
          params.put("offset", "7481");
          logHandler.handleSearchLog(null, params);
      }
  
  }
  