使用 Java 实现一个极简的静态资源 HTTP 服务器
发布于 2024-12-29
有的时候需要在本机起一个简易的 HTTP
服务器,只需要简单的访问静态资源就可以。
比如,自建一些镜像站点,如 emacs
插件镜像, gradle wrapper
镜像等。
平时在自己的电脑上会用一些简单的方法,比如 PHP 内置的 HTTP 服务器。
只需要一个简单的命令 php -S 127.0.0.1:8080
就可以在当前工作目录启动一个 HTTP
服务。
最近在离线的电脑上也有这个诉求,电脑无法联网,没有安装 nginx
或 php
,但是有 Java
开发环境。
于是考虑用 Java
起一个简易的 HTTP
服务,且尽量不引入外部依赖。
需求分析
目标是构建一个简单的 HTTP
服务器,它能够:
- 监听指定端口(默认为 8080)
- 提供从指定目录(默认为当前目录)读取的静态文件
- 支持通过命令行参数动态指定端口和静态文件目录
- 简单的访问日志记录
代码编写
import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpExchange; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; public class SimpleHttpServer { private static int port = 8080; // 默认端口 private static String root = "."; // 默认静态资源目录 public static void main(String[] args) throws IOException { // 解析命令行参数 parseCommandLineArgs(args); // 将 root 转换为绝对路径并检查目录是否存在 Path rootPath = validateAndGetAbsolutePath(root); // 启动 HTTP 服务器 startServer(port, rootPath); } private static void parseCommandLineArgs(String[] args) { Map<String, String> argsMap = new HashMap<>(); for (String arg : args) { String[] splitArg = arg.split("="); if (splitArg.length == 2) { argsMap.put(splitArg[0], splitArg[1]); } } // 提取端口号和静态目录 if (argsMap.containsKey("--port")) { try { port = Integer.parseInt(argsMap.get("--port")); } catch (NumberFormatException e) { System.out.println("Invalid port number, using default port 8080"); } } if (argsMap.containsKey("--root")) { root = argsMap.get("--root"); } } private static Path validateAndGetAbsolutePath(String rootDir) { // 获取相对路径并转换为绝对路径 Path path = Paths.get(rootDir).toAbsolutePath(); // 检查目录是否存在 if (!Files.exists(path) || !Files.isDirectory(path)) { System.out.println("Error: The directory does not exist or is not a directory: " + path); System.exit(1); // 目录无效,退出程序 } return path; } private static void startServer(int port, Path rootPath) throws IOException { // 创建 HTTP 服务器,监听指定的端口 HttpServer server = HttpServer.create(new java.net.InetSocketAddress(port), 0); // 设置 handler,处理所有的 HTTP 请求 server.createContext("/", new HttpHandler() { @Override public void handle(HttpExchange exchange) throws IOException { // 获取请求的文件路径 String requestedFile = exchange.getRequestURI().getPath(); if (requestedFile.equals("/")) { requestedFile = "/index.html"; // 默认返回 index.html } Path filePath = Paths.get(rootPath.toString() + requestedFile); if (Files.exists(filePath) && !Files.isDirectory(filePath)) { // 文件存在则读取并返回 exchange.sendResponseHeaders(200, Files.size(filePath)); try (InputStream inputStream = Files.newInputStream(filePath); OutputStream outputStream = exchange.getResponseBody()) { Files.copy(filePath, outputStream); } System.out.println("200 - File found and served: " + filePath); } else { // 文件不存在则返回 404,并打印日志 String response = "404 Not Found"; exchange.sendResponseHeaders(404, response.getBytes().length); exchange.getResponseBody().write(response.getBytes()); System.out.println("404 - File not found: " + filePath); } exchange.getResponseBody().close(); } }); // 启动服务器 server.start(); System.out.println("Server started at http://localhost:" + port); System.out.println("Serving static files from: " + rootPath); } }
编译
javac SimpleHttpServer.java
运行
java SimpleHttpServer
可以看到输出
Server started at http://localhost:8080 Serving static files from: /tmp/simple-http-server/.
尝试浏览器访问正常,搞定。
也可以启动时指定端口和服务根目录
java SimpleHttpServer --port=8090 --root=/tmp
如果还想打成 Jar
包的话,还可以继续这么干…
打包后运行
创建
MANIFEST.MF
文件,指定入口类Main-Class
。MANIFEST.MF
文件内容:Manifest-Version: 1.0 Main-Class: SimpleHttpServer
打包为
JAR
文件:jar cvfm SimpleHttpServer.jar MANIFEST.MF SimpleHttpServer*.class
运行
JAR
文件:java -jar SimpleHttpServer.jar --port=8080 --root=/path/to/static