我们将实现一个简单的 HTTP/1.1 客户端,它可以向指定服务器发送 GET 请求,并接收和解析响应。

核心思想
HTTP 协议是构建在 TCP 协议之上的应用层协议,使用 Java Socket 实现 HTTP 的核心步骤如下:
- 建立 TCP 连接:使用
java.net.Socket类与目标服务器的指定端口(通常是 80 或 443)建立连接。 - 构造 HTTP 请求:按照 HTTP 协议的格式,构建一个请求字符串,这包括请求行(如
GET / HTTP/1.1)、请求头(如Host: www.example.com)和可选的请求体(GET 请求通常没有)。 - 发送请求:通过
Socket获取输出流,将构造好的 HTTP 请求字符串发送给服务器。 - 接收响应:通过
Socket获取输入流,读取服务器返回的 HTTP 响应,响应同样由响应行、响应头和响应体组成。 - 解析响应:将输入流中的数据解析成我们可以理解的格式,例如提取状态码、响应头和内容。
- 关闭连接:操作完成后,关闭 Socket 和相关的输入输出流。
第一步:创建 HTTP 客户端类
我们将创建一个名为 SimpleHttpClient 的类。
添加必要的导入
import java.io.*; import java.net.*; import java.util.HashMap; import java.util.Map; import java.util.Scanner;
定义客户端类结构
public class SimpleHttpClient {
public static void main(String[] args) {
// 主方法,用于测试我们的客户端
try {
// 向 www.example.com 的 80 端口发起 GET 请求,请求根路径 "/"
String response = sendHttpRequest("www.example.com", 80, "/", "GET");
System.out.println("--- HTTP Response ---");
System.out.println(response);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 发送 HTTP 请求的核心方法
* @param host 服务器主机名或 IP 地址
* @param port 服务器端口
* @param path 请求的资源路径
* @param method 请求方法 (GET, POST, etc.)
* @return 服务器返回的完整 HTTP 响应字符串
*/
public static String sendHttpRequest(String host, int port, String path, String method) throws IOException {
// ... 实现代码将在这里 ...
}
}
第二步:实现 sendHttpRequest 方法
这是整个实现的核心,我们将逐步填充这个方法。
建立 TCP 连接
使用 Socket 连接到服务器,我们使用 try-with-resources 语句来自动关闭 Socket 和流,确保资源被正确释放。

// sendHttpRequest 方法内部
Socket socket = null;
try (Socket sock = new Socket(host, port)) {
socket = sock; // 如果需要在外部访问socket,可以这样赋值,否则直接在try-with-resources中使用
// ... 后续代码将在这里 ...
} finally {
// try-with-resources 会自动关闭,所以这里可以不用写
}
构造 HTTP 请求
一个完整的 HTTP GET 请求看起来像这样:
GET / HTTP/1.1
Host: www.example.com
Connection: close
User-Agent: SimpleJavaHttpClient/1.0
- 请求行:
GET / HTTP/1.1GET是方法。- 是请求的资源路径。
HTTP/1.1是协议版本。
- 请求头:
Host是必须的,它告诉服务器我们请求的是哪个域名。Connection: close表示请求完成后关闭 TCP 连接。User-Agent标识客户端类型。 - 空行:
<CR><LF><CR><LF>(即\r\n\r\n)是请求头和请求体之间的分隔符,对于 GET 请求,请求体为空,所以空行后直接结束。
我们用 Java 字符串来构造它:
// 在 try-with-resources 块内部
// 1. 构造请求
StringBuilder requestBuilder = new StringBuilder();
requestBuilder.append(method).append(" ").append(path).append(" HTTP/1.1\r\n");
requestBuilder.append("Host: ").append(host).append("\r\n");
requestBuilder.append("Connection: close\r\n"); // 告诉服务器在响应后关闭连接
requestBuilder.append("User-Agent: SimpleJavaHttpClient/1.0\r\n"); // 自定义的客户端标识
requestBuilder.append("\r\n"); // 空行,表示请求头结束
String httpRequest = requestBuilder.toString();
发送请求
通过 Socket 的 getOutputStream() 获取输出流,并将 httpRequest 字符串写入。
// 在 try-with-resources 块内部
// 2. 发送请求
OutputStream out = socket.getOutputStream();
out.write(httpRequest.getBytes());
out.flush(); // 确保所有数据都被发送
System.out.println("--- Request Sent ---");
System.out.println(httpRequest);
接收并解析响应
服务器返回的响应格式如下:

HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 1256
Connection: close
... (其他响应头) ...
<!DOCTYPE html>
<html>
... (HTML内容) ...
</html>
解析策略:
- 读取响应行:读取第一行,直到遇到
\r\n,可以解析出版本、状态码和状态文本。 - 读取响应头:循环读取后续的每一行,直到遇到一个空行
\r\n,每一行都可以按 分割成键和值。 - 读取响应体:空行之后的所有内容都是响应体,由于我们设置了
Connection: close,可以简单地读取输入流直到末尾。
// 在 try-with-resources 块内部
// 3. 接收和解析响应
StringBuilder responseBuilder = new StringBuilder();
InputStream in = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
// 读取状态行
String statusLine = reader.readLine();
if (statusLine == null) {
throw new IOException("Empty response from server");
}
responseBuilder.append(statusLine).append("\r\n");
// 读取响应头
String headerLine;
while ((headerLine = reader.readLine()) != null && !headerLine.isEmpty()) {
responseBuilder.append(headerLine).append("\r\n");
}
// 读取空行
responseBuilder.append("\r\n");
// 读取响应体
int ch;
while ((ch = in.read()) != -1) {
responseBuilder.append((char) ch);
}
String httpResponse = responseBuilder.toString();
return httpResponse;
完整代码
将以上所有部分组合起来,我们得到最终的 SimpleHttpClient 类。
import java.io.*;
import java.net.*;
public class SimpleHttpClient {
public static void main(String[] args) {
// 测试 GET 请求
try {
System.out.println("Sending GET request to www.example.com...");
String response = sendHttpRequest("www.example.com", 80, "/", "GET");
System.out.println("\n--- Full HTTP Response ---");
System.out.println(response);
// 解析并打印响应头
System.out.println("\n--- Parsed Response Headers ---");
parseResponseHeaders(response);
} catch (IOException e) {
e.printStackTrace();
}
}
public static String sendHttpRequest(String host, int port, String path, String method) throws IOException {
// 使用 try-with-resources 确保 Socket, OutputStream, InputStream 等资源被自动关闭
try (Socket socket = new Socket(host, port);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(in))) {
// 1. 构造 HTTP 请求
StringBuilder requestBuilder = new StringBuilder();
requestBuilder.append(method).append(" ").append(path).append(" HTTP/1.1\r\n");
requestBuilder.append("Host: ").append(host).append("\r\n");
requestBuilder.append("Connection: close\r\n");
requestBuilder.append("User-Agent: SimpleJavaHttpClient/1.0\r\n");
requestBuilder.append("\r\n"); // 空行,结束请求头
String httpRequest = requestBuilder.toString();
System.out.println("--- HTTP Request ---");
System.out.print(httpRequest);
// 2. 发送请求
out.write(httpRequest.getBytes());
out.flush();
// 3. 接收和解析响应
StringBuilder responseBuilder = new StringBuilder();
String line;
// 读取状态行
line = reader.readLine();
if (line == null) {
throw new IOException("Empty response from server."); 