java 使用ServerSocket 根据 浏览器 请求,做出响应
基于 Java 的简单 HTTP 服务器实现,响应 GET 请求,返回固定简单html,并支持静态资源(如 favicon.ico)的返回。
关键技术点
-
HTTP 协议解析:
- 手动读取并解析 HTTP 请求行和请求头。
- 支持基础的 URL 路径判断(如 /favicon.ico)。
-
字符编码处理:
- 使用
StandardCharsets.UTF_8
统一处理输入输出编码,避免乱码问题。
- 使用
-
时区与时间格式化:
- 使用
ZonedDateTime
和ZoneId.of("UTC")
构造符合 RFC 1123 标准的日期头字段。
- 使用
-
静态资源服务:
- 提供对 favicon.ico 的特殊处理,支持二进制文件发送。
-
异常处理机制:
- 捕获并打印服务器启动和请求处理中的异常信息。
- 返回对应的错误页面(如 405、500)。
启动方式:
java Test1
默认监听端口:8080
默认网站根目录:www/
需确保 www/favicon.ico
存在,否则将返回空响应。
测试访问:
- 访问
http://localhost:8080
将显示欢迎语句。
该项目用途:
- 学习 HTTP 协议底层原理。
项目代码:
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.time.ZoneId;
import java.time.ZonedDateTime;
public class Test1 {
private static final int PORT = 8080;
private static final String ROOT_DIR = "www";
private static final String FAVICON_PATH = ROOT_DIR + "/favicon.ico";
public static void main(String[] args) {
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
System.out.println("服务器启动,监听端口: " + PORT);
while (true) {
try (Socket clientSocket = serverSocket.accept()) {
System.out.println("客户端连接: " + clientSocket.getInetAddress());
BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream(), StandardCharsets.UTF_8));
PrintWriter out = new PrintWriter(
new OutputStreamWriter(clientSocket.getOutputStream(), StandardCharsets.UTF_8), true);
String requestLine = in.readLine();
if (requestLine == null) continue;
System.out.println("请求行: " + requestLine);
String[] requestParts = requestLine.split(" ");
if (requestParts.length < 2) continue;
String method = requestParts[0];
String path = requestParts[1];
// 读取请求头(忽略)
String headerLine;
while ((headerLine = in.readLine()) != null && !headerLine.isEmpty()) {
System.out.println("请求头: " + headerLine);
}
if ("GET".equalsIgnoreCase(method)) {
//handleGetRequest(path, out, clientSocket.getOutputStream());
// 特别处理favicon.ico
if ("/favicon.ico".equals(path)) {
handleFaviconRequest(out, clientSocket.getOutputStream());
return;
}
sendResponse(out, 200, "text/html", "<h1>欢迎使用虚拟线程Web服务器</h1>");
} else {
sendResponse(out, 405, "Method Not Allowed", "该方法不被支持");
}
} catch (IOException e) {
System.err.println("处理请求时出错: " + e.getMessage());
}
}
} catch (IOException e) {
System.err.println("服务器启动失败: " + e.getMessage());
}
}
private static void handleFaviconRequest(PrintWriter out, OutputStream rawOut) {
File favicon = new File(FAVICON_PATH);
if (favicon.exists() && favicon.isFile()) {
try {
// 使用ZonedDateTime并指定时区为UTC
String date = ZonedDateTime.now(ZoneId.of("UTC"))
.format(DateTimeFormatter.RFC_1123_DATE_TIME);
// 发送响应头
StringBuilder responseHeader = new StringBuilder();
responseHeader.append("HTTP/1.1 200 OK\r\n");
responseHeader.append("Date: ").append(date).append("\r\n");
responseHeader.append("Server: SimpleTCPServer/1.0\r\n");
responseHeader.append("Content-Type: image/x-icon\r\n");
responseHeader.append("Content-Length: ").append(favicon.length()).append("\r\n");
responseHeader.append("Connection: close\r\n");
responseHeader.append("\r\n");
// 发送响应头
out.print(responseHeader.toString());
out.flush();
// 发送图标二进制数据
try (FileInputStream fis = new FileInputStream(favicon)) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
rawOut.write(buffer, 0, bytesRead);
}
}
rawOut.flush();
} catch (IOException e) {
sendResponse(out, 500, "Internal Server Error", "服务器内部错误");
}
} else {
// 如果没有提供图标,返回一个空的图标响应
sendEmptyFaviconResponse(out);
}
}
private static void sendEmptyFaviconResponse(PrintWriter out) {
// 使用ZonedDateTime并指定时区为UTC
String date = ZonedDateTime.now(ZoneId.of("UTC"))
.format(DateTimeFormatter.RFC_1123_DATE_TIME);
String response = """
HTTP/1.1 200 OK
Date: %s
Server: SimpleTCPServer/1.0
Content-Type: image/x-icon
Content-Length: 0
Connection: close
""".formatted(date);
out.print(response);
out.flush();
}
private static void sendResponse(PrintWriter out, int statusCode, String contentType, String content) {
String statusLine = "HTTP/1.1 " + statusCode + " " + getStatusMessage(statusCode);
// 使用ZonedDateTime并指定时区为UTC
String date = ZonedDateTime.now(ZoneId.of("UTC"))
.format(DateTimeFormatter.RFC_1123_DATE_TIME);
StringBuilder response = new StringBuilder();
response.append(statusLine).append("\r\n");
response.append("Date: ").append(date).append("\r\n");
response.append("Server: SimpleTCPServer/1.0\r\n");
response.append("Content-Type: ").append(contentType).append("; charset=utf-8\r\n");
response.append("Content-Length: ").append(content.getBytes().length).append("\r\n");
response.append("Connection: close\r\n");
response.append("\r\n");
response.append(content);
out.print(response.toString());
out.flush();
}
private static String getStatusMessage(int statusCode) {
return switch (statusCode) {
case 200 -> "OK";
case 404 -> "Not Found";
case 405 -> "Method Not Allowed";
case 500 -> "Internal Server Error";
default -> "Unknown Status";
};
}
}