public class JHttp {

  private static final Logger logger = Logger.getLogger(JHttp.class.getCanonicalName());

  private static final int NUM_THREADS = 50;

  private static final String INDEX_FILE = "index.html";

  private final File rootDirectory;
  private final int port;

  public JHttp(File rootDirectory, int port) {
    this.rootDirectory = rootDirectory;
    this.port = port;
  }

  public void start() throws IOException {
    ExecutorService pool = Executors.newFixedThreadPool(NUM_THREADS);

    try (ServerSocket server = new ServerSocket(port)) {
      logger.info("Accepting connections on port " + server.getLocalPort());
      logger.info("Document Root: " + rootDirectory);

      while (true) {
        try {
          Socket request = server.accept();
          RequestProcessor r = new RequestProcessor(rootDirectory, INDEX_FILE, request);
          pool.submit(r);
        } catch (IOException e) {
          logger.log(Level.WARNING, "Error accepting connection", e);
        }
      }
    }
  }

  public static void main(String[] args) {
    try {
      URL url = JHttp.class.getClassLoader().getResource("static");
      JHttp jHttp = new JHttp(new File(url.getPath()), 8888);
      jHttp.start();
    } catch (IOException e) {
      logger.log(Level.SEVERE, "server could not start", e);
    }
  }

  private class RequestProcessor implements Runnable {

    private File rootDirectory;
    private String indexFileName = "index.html";
    private Socket connection;


    public RequestProcessor(File rootDirectory, String indexFileName, Socket connection) {
      this.rootDirectory = rootDirectory;
      this.indexFileName = indexFileName;
      this.connection = connection;
    }

    @Override
    public void run() {
      // 安全检查省略

      String root = rootDirectory.getPath();
      try {
        OutputStream raw = new BufferedOutputStream(connection.getOutputStream());
        Writer out = new OutputStreamWriter(raw);
        Reader in = new InputStreamReader(new BufferedInputStream(connection.getInputStream()));

        // 取请求头的第一行 GET /xxx HTTP/1.1
        StringBuilder requestLine = new StringBuilder();
        while (true) {
          int c = in.read();
          if (c == '\r' || c == '\n') {
            break;
          }
          requestLine.append((char) c);
        }

        String get = requestLine.toString();
        logger.info(connection.getRemoteSocketAddress() + " " + get);
        String[] tokens = get.split("\\s+");
        String method = tokens[0];
        String version = "";
        if ("GET".equals(method)) {
          String fileName = tokens[1];
          if (fileName.endsWith("/")) {
            fileName += indexFileName;// 给默认边界值
          }
          // 映射MIME类型
          String contentType = URLConnection.getFileNameMap().getContentTypeFor(fileName);

          if (tokens.length > 2) {
            version = tokens[2];
          }

          File theFile = new File(rootDirectory, fileName.substring(1, fileName.length()));
          if (theFile.canRead() && theFile.getCanonicalPath().startsWith(root)) {
            // 不让客户端超出文档边界,安全考虑
            byte[] theData = Files.readAllBytes(theFile.toPath());
            if (version.startsWith("HTTP/")) { // 发送一个MIME首部
              sendHeader(out, "HTTP/1.1 200 OK", contentType, theData.length);
            }

            // 发送文件,可能是图像或二进制数据,不能使用writer
            raw.write(theData);
            raw.flush();
          } else {
            // 无法找到文件
            String body = new StringBuilder("<html>\r\n")
                    .append("<head><title>File not Find</title></head>\r\n")
                    .append("<body>")
                    .append("<h1>HTTP Error 404 : File Not Found</h1>\r\n")
                    .append("</body></html>\r\n")
                    .toString();
            sendHeader(out, "HTTP/1.1 404 FileNotFound", "text/html; charset=utf-8", body.length());
            out.write(body);
            out.flush();
          }
        } else {
          // 不支持的请求动作
          // 无法找到文件
          String body = new StringBuilder("<html>\r\n")
                  .append("<head><title>File not Find</title></head>\r\n")
                  .append("<body>")
                  .append("<h1>HTTP Error 501 : Not Support</h1>\r\n")
                  .append("</body></html>\r\n")
                  .toString();
          sendHeader(out, "HTTP/1.1 501 Not Support", "text/html; charset=utf-8", body.length());
          out.write(body);
          out.flush();
        }

        out.close();
        in.close();
      } catch (IOException e) {
        logger.log(Level.WARNING, "Error talking to " + connection.getRemoteSocketAddress(), e);
      } finally {
        try {
          connection.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    }

    private void sendHeader(Writer out, String responseCode, String contentType, int length) throws IOException {
      out.write(responseCode + System.lineSeparator());
      out.write("Date: " + new Date() + System.lineSeparator());
      out.write("Server: JHTTP 1.0" + System.lineSeparator());
      out.write("Content-length: " + length + System.lineSeparator());
      out.write("Content-type: " + contentType + System.lineSeparator() + System.lineSeparator()); // 响应头和响应体之间多一行空行
      out.flush();
    }
  }

}

后续可追加特性:
1.服务器管理页面
2.支持Java Servlet API
3.支持其它请求方法,如POST,PUT
4.支持多文档根目录


花溪的小石头
180 声望8 粉丝

境由心造,一切只在当下的转念之间。