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.支持多文档根目录
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。