下面我将从客户端和服务器端两个角度,为你详细解析如何使用 Java 处理 HTTP 协议,并提供从基础到高级的多种方案。
核心概念:HTTP 协议是什么?
在开始编码前,我们先简单回顾一下 HTTP 协议,一个 HTTP 请求/响应主要由三部分组成:
- 起始行:
- 请求行:
GET /index.html HTTP/1.1 - 状态行:
HTTP/1.1 200 OK
- 请求行:
- 头部:
Host: www.example.comUser-Agent: Mozilla/5.0Content-Type: application/json- 以一个空行结束。
请求的数据(如 POST 请求的表单数据、JSON)或响应的数据(如 HTML 文件、JSON 响应)。
“解析”HTTP 协议,就是读取这些部分,并根据它们的内容进行相应的处理。
客户端:如何发送 HTTP 请求并解析响应
作为客户端,你的目标是向服务器发送一个请求,并正确处理服务器返回的响应。
使用 java.net.HttpURLConnection (JDK 内置,无需额外依赖)
这是 Java 标准库自带的、最基础的 HTTP 客户端实现,它功能齐全但代码相对繁琐。
特点:
- 优点: 无需任何外部库,随 JDK 一起发布。
- 缺点: API 陈旧,使用起来比较麻烦,处理连接、流、编码等需要手动编写大量样板代码,不支持 HTTP/2。
示例代码:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
public class JdkHttpExample {
public static void main(String[] args) throws Exception {
// 1. 创建 URL 对象
URL url = new URL("https://jsonplaceholder.typicode.com/posts/1");
// 2. 打开连接
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET"); // 设置请求方法
connection.setRequestProperty("User-Agent", "MyJavaApp/1.0"); // 设置请求头
// 3. 获取响应码
int responseCode = connection.getResponseCode();
System.out.println("Response Code: " + responseCode);
// 4. 读取响应数据
if (responseCode == HttpURLConnection.HTTP_OK) { // 200
// 使用 try-with-resources 自动关闭流
try (BufferedReader in = new BufferedReader(
new InputStreamReader(connection.getInputStream()))) {
String inputLine;
StringBuilder response = new StringBuilder();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
// 5. 解析响应体 (这里是 JSON 字符串)
System.out.println("Response Body:");
System.out.println(response.toString());
}
} else {
System.out.println("GET request failed");
}
// 6. 断开连接
connection.disconnect();
}
}
使用 Apache HttpClient (功能强大,业界标准)
这是 Apache 基金会维护的、功能非常强大的 HTTP 客户端,它比 HttpURLConnection 更现代、更易用、更灵活。
特点:
- 优点: API 设计优秀,功能强大(支持连接池、重试、高级认证等),性能更好,支持 HTTP/1.1 和 HTTP/2。
- 缺点: 需要添加外部依赖。
Maven 依赖:
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.3.1</version> <!-- 使用最新版本 -->
</dependency>
示例代码:
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import org.apache.hc.core5.http.io.entity.StringEntity;
public class ApacheHttpClientExample {
public static void main(String[] args) throws Exception {
// 1. 创建 HttpClient 实例
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
// 2. 创建请求方法对象
HttpGet request = new HttpGet("https://jsonplaceholder.typicode.com/posts/1");
request.addHeader("User-Agent", "MyJavaApp/1.0");
// 3. 执行请求并获取响应
try (CloseableHttpResponse response = httpClient.execute(request)) {
// 4. 获取响应状态码
System.out.println("Response Code: " + response.getCode());
// 5. 获取响应体实体
// 使用 EntityUtils.toString() 直接将响应体读为字符串,非常方便
String responseBody = EntityUtils.toString(response.getEntity());
System.out.println("Response Body:");
System.out.println(responseBody);
}
}
}
}
发送 POST 请求示例:
// ... (创建 httpClient)
HttpPost httpPost = new HttpPost("https://jsonplaceholder.typicode.com/posts");
// 设置请求体 (JSON)
String jsonPayload = "{\"title\":\"foo\",\"body\":\"bar\",\"userId\":1}";
httpPost.setEntity(new StringEntity(jsonPayload));
httpPost.setHeader("Content-Type", "application/json; charset=UTF-8");
// ... (执行请求)
使用 OkHttp (现代、高效、易用)
OkHttp 是目前非常流行的一个 HTTP 客户端,以其简洁的 API 和出色的性能著称。
特点:
- 优点: API 极其简洁(链式调用),支持同步/异步请求,自动处理 GZIP,支持 HTTP/2,性能优异。
- 缺点: 需要添加外部依赖。
Maven 依赖:
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version> <!-- 使用最新版本 -->
</dependency>
示例代码:
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class OkHttpExample {
public static void main(String[] args) throws Exception {
// 1. 创建 OkHttpClient 实例 (可以复用)
OkHttpClient client = new OkHttpClient();
// 2. 构建请求
Request request = new Request.Builder()
.url("https://jsonplaceholder.typicode.com/posts/1")
.header("User-Agent", "MyJavaApp/1.0")
.build();
// 3. 执行请求 (同步)
try (Response response = client.newCall(request).execute()) {
// 4. 检查是否成功
if (!response.isSuccessful()) {
throw new IOException("Unexpected code " + response);
}
// 5. 获取响应体
String responseBody = response.body().string();
System.out.println("Response Body:");
System.out.println(responseBody);
}
}
}
异步请求示例:
// ... (创建 client 和 request)
client.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
e.printStackTrace();
}
@Override
public void onResponse(Call call, Response response) throws IOException {
try (response) {
if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
String responseBody = response.body().string();
System.out.println("Async Response Body: " + responseBody);
}
}
});
服务器端:如何接收 HTTP 请求并解析
作为服务器端,你需要创建一个服务来监听端口,接收客户端的请求,解析它,并返回响应。
使用 java.net.ServerSocket (从零开始,理解协议)
这种方式让你完全控制协议的每一个细节,非常适合学习和理解 HTTP 协议的底层原理。
特点:
- 优点: 无需框架,能让你深刻理解 HTTP 协议的格式。
- 缺点: 非常繁琐,需要手动处理多线程、连接、请求解析、响应构建等,不适合生产环境。
示例代码 (一个极简的 HTTP 服务器):
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class SimpleHttpServer {
public static void main(String[] args) throws IOException {
int port = 8080;
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("Server is listening on port " + port);
while (true) {
Socket clientSocket = serverSocket.accept();
// 为每个客户端连接创建一个新线程处理
new Thread(() -> handleRequest(clientSocket)).start();
}
}
}
private static void handleRequest(Socket clientSocket) {
try (
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)
) {
// 1. 读取请求行
String requestLine = in.readLine();
System.out.println("Received request: " + requestLine);
// 2. 读取请求头 (直到遇到空行)
String headerLine;
while ((headerLine = in.readLine()) != null && !headerLine.isEmpty()) {
System.out.println("Header: " + headerLine);
}
// 3. 构建响应
String httpResponse = "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/plain\r\n" +
"Connection: close\r\n" +
"\r\n" +
"Hello from a simple Java HTTP server!";
out.println(httpResponse);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
你可以用浏览器或 curl http://localhost:8080 来测试这个服务器。
使用 Servlet 容器 (生产环境标准)
在生产环境中,没有人会从零开始写服务器,我们使用成熟的 Web 框架,其中最核心的就是 Servlet。
工作流程:
- 你选择一个 Servlet 容器(如 Tomcat、Jetty、Undertow)。
- 你编写一个
HttpServlet类,并重写doGet,doPost等方法。 - 容器负责接收原始 HTTP 请求,并将其解析成一个
HttpServletRequest对象。 - 容器负责构建一个
HttpServletResponse对象。 - 容器调用你的
servlet.service()方法,并将这两个对象作为参数传入。
你只需要关注业务逻辑,所有底层的解析工作都由容器完成了。
Maven 依赖 (以 Tomcat 为例):
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.0.0</version>
<scope>provided</scope> <!-- 由运行环境提供 -->
</dependency>
示例代码 (一个简单的 Servlet):
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/hello") // 定义 URL 映射
public class HelloServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
// 1. 从 request 中获取请求参数
String name = request.getParameter("name");
if (name == null || name.isEmpty()) {
name = "World";
}
// 2. 设置响应类型和状态码
response.setContentType("text/plain");
response.setStatus(HttpServletResponse.SC_OK);
// 3. 将响应内容写入输出流
response.getWriter().println("Hello, " + name + "!");
}
}
当访问 http://localhost:8080/your-app-context/hello?name=Java 时,浏览器会收到 Hello, Java!。
总结与选择
| 场景 | 推荐方案 | 优点 | 缺点 |
|---|---|---|---|
| 客户端 - 快速实现 | OkHttp | API 极简,功能强大,性能好 | 需要引入第三方库 |
| 客户端 - 企业级/复杂逻辑 | Apache HttpClient | 功能最全面,稳定,可定制性强 | API 比 OkHttp 稍显复杂 |
| 客户端 - 无依赖环境 | HttpURLConnection |
JDK 自带,无依赖 | API 陈旧,使用繁琐,功能有限 |
| 服务器端 - 学习/教学 | ServerSocket |
深入理解 HTTP 协议 | 繁琐,不适合生产 |
| 服务器端 - 生产环境 | Servlet 容器 (Tomcat等) | 标准化,稳定,生态完善,性能好 | 需要部署应用服务器,配置相对复杂 |
给你的建议:
- 如果你只是想写个客户端去调用一个 API:直接使用 OkHttp,它会让你事半功倍。
- 如果你在做一个企业级后端项目:使用 Servlet (如 Tomcat) 作为基础,并结合 OkHttp 或 Apache HttpClient 来调用外部服务。
- 如果你想面试或深入理解网络编程:尝试用
ServerSocket手动实现一个简单的 HTTP 服务器,这个经历会让你对 HTTP 协议有质的飞跃。
