下面我将分步进行讲解,从最基础的原理开始,到具体的代码实现,最后给出一个更完整的工具类。

核心原理
HTTP 协议是构建在 TCP/IP 协议之上的应用层协议,当我们使用浏览器访问一个网站时,浏览器实际上就是扮演了一个 HTTP 客户端的角色,它通过 Socket 与服务器的 80 (HTTP) 或 443 (HTTPS) 端口建立 TCP 连接,然后按照 HTTP 协议的格式发送请求,并接收服务器的响应。
模拟 HTTP 请求,就是手动完成浏览器做的事情:
- 创建 Socket 连接:指定目标主机(域名或IP)和端口。
- 获取输出流:通过 Socket 获取
OutputStream,用于向服务器发送 HTTP 请求报文。 - 构建 HTTP 请求报文:按照 HTTP 协议的格式(请求行、请求头、空行、请求体)手动拼接字符串,并将其转换为字节写入输出流。
- 获取输入流:通过 Socket 获取
InputStream,用于读取服务器返回的 HTTP 响应报文。 - 解析 HTTP 响应报文:从输入流中读取数据,并按照 HTTP 协议的格式解析响应状态码、响应头和响应体。
- 关闭资源:关闭 Socket 和相关的输入输出流。
HTTP 请求/响应报文格式
在写代码前,我们必须清楚 HTTP 报文长什么样。
HTTP 请求报文示例 (GET 请求)
GET /index.html HTTP/1.1 Host: www.example.com User-Agent: MyCustomJavaClient/1.0 Accept: */* (空行)
- 请求行:
GET /index.html HTTP/1.1GET: 请求方法 (还有 POST, PUT, DELETE 等)/index.html: 请求的 URI (统一资源标识符)HTTP/1.1: HTTP 协议版本
- 请求头:
Host: ...,User-Agent: ...等,每个请求头占一行,以Key: Value的形式出现。 - 空行: 一个单独的回车换行符
\r\n,这是请求头和请求体之间的分隔符,对于 GET 请求,请求体为空,所以空行后直接结束。
HTTP 请求报文示例 (POST 请求)
POST /api/login HTTP/1.1
Host: www.example.com
Content-Type: application/json
Content-Length: 38
{"username":"admin","password":"123456"}
- 请求体: 空行之后的内容就是请求体,对于 POST 请求,这里通常包含要提交给服务器的数据。
Content-Length头部非常重要,它告诉服务器请求体有多少个字节。
HTTP 响应报文示例
HTTP/1.1 200 OK
Content-Type: text/html; charset=UTF-8
Content-Length: 1276
Connection: keep-alive
Date: Wed, 27 Oct 2025 10:00:00 GMT
<!DOCTYPE html>
<html>
<head>Welcome to Example.com</title>
</head>
<body>
<h1>Hello, World!</h1>
...
</body>
</html>
- 状态行:
HTTP/1.1 200 OKHTTP/1.1: 协议版本200: 状态码 (表示成功)OK: 状态码描述
- 响应头:
Content-Type: ...,Content-Length: ...等。 - 空行: 分隔响应头和响应体。
- 响应体: 空行之后的内容,即服务器返回的实际数据(HTML, JSON, 图片等)。
Java 代码实现
下面我们来实现一个简单的 HTTP 客户端,它可以发送 GET 和 POST 请求。

第1步:发送一个简单的 GET 请求
这个例子会向 httpbin.org/get 发送一个 GET 请求,该网站会返回我们请求的详细信息,非常适合测试。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class SimpleHttpClientGet {
public static void main(String[] args) {
String host = "httpbin.org";
int port = 80;
String path = "/get";
try (Socket socket = new Socket(host, port);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
// 1. 构建HTTP请求报文
StringBuilder request = new StringBuilder();
request.append("GET " + path + " HTTP/1.1\r\n");
request.append("Host: " + host + "\r\n");
request.append("Accept: */*\r\n");
request.append("Connection: close\r\n"); // 请求结束后关闭连接
request.append("\r\n"); // 空行,代表请求头结束
// 2. 发送请求
out.print(request.toString());
out.flush(); // 确保数据被立即发送
System.out.println("Request sent:\n" + request.toString());
// 3. 读取并打印响应
String line;
System.out.println("\n--- Response from Server ---");
// 先读取响应头,直到遇到空行
while ((line = in.readLine()) != null && !line.isEmpty()) {
System.out.println(line);
}
// 空行之后是响应体
System.out.println("\n--- Response Body ---");
StringBuilder responseBody = new StringBuilder();
while ((line = in.readLine()) != null) {
System.out.println(line);
responseBody.append(line);
}
// 在实际应用中,你可以在这里解析JSON响应体
// System.out.println("Response Body JSON: " + responseBody.toString());
} catch (UnknownHostException e) {
System.err.println("Don't know about host " + host);
e.printStackTrace();
} catch (IOException e) {
System.err.println("Couldn't get I/O for the connection to " +
host);
e.printStackTrace();
}
}
}
第2步:发送一个 POST 请求
这个例子会向 httpbin.org/post 发送一个 POST 请求,并附带 JSON 格式的请求体。
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class SimpleHttpClientPost {
public static void main(String[] args) {
String host = "httpbin.org";
int port = 80;
String path = "/post";
String jsonBody = "{\"username\":\"testuser\",\"password\":\"password123\"}";
try (Socket socket = new Socket(host, port);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
// 1. 构建HTTP请求报文
StringBuilder request = new StringBuilder();
request.append("POST " + path + " HTTP/1.1\r\n");
request.append("Host: " + host + "\r\n");
request.append("Content-Type: application/json\r\n"); // 告诉服务器我们发送的是JSON
request.append("Content-Length: " + jsonBody.length() + "\r\n"); // 告诉服务器请求体长度
request.append("Accept: */*\r\n");
request.append("Connection: close\r\n");
request.append("\r\n"); // 空行
request.append(jsonBody); // 请求体
// 2. 发送请求
out.print(request.toString());
out.flush();
System.out.println("Request sent:\n" + request.toString());
// 3. 读取并打印响应
System.out.println("\n--- Response from Server ---");
String line;
// 读取响应头
while ((line = in.readLine()) != null && !line.isEmpty()) {
System.out.println(line);
}
// 读取响应体
System.out.println("\n--- Response Body ---");
while ((line = in.readLine()) != null) {
System.out.println(line);
}
} catch (UnknownHostException e) {
System.err.println("Don't know about host " + host);
e.printStackTrace();
} catch (IOException e) {
System.err.println("Couldn't get I/O for the connection to " + host);
e.printStackTrace();
}
}
}
封装成一个可重用的工具类
上面的例子虽然能工作,但每次使用都很冗长,我们可以将其封装成一个简单的 HttpClient 工具类,方便调用。
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import java.net.UnknownHostException

