杰瑞科技汇

Java如何解析HTTP协议?

下面我将从客户端服务器端两个角度,为你详细解析如何使用 Java 处理 HTTP 协议,并提供从基础到高级的多种方案。


核心概念:HTTP 协议是什么?

在开始编码前,我们先简单回顾一下 HTTP 协议,一个 HTTP 请求/响应主要由三部分组成:

  1. 起始行:
    • 请求行: GET /index.html HTTP/1.1
    • 状态行: HTTP/1.1 200 OK
  2. 头部:
    • Host: www.example.com
    • User-Agent: Mozilla/5.0
    • Content-Type: application/json
    • 以一个空行结束。
  3. 请求的数据(如 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

工作流程:

  1. 你选择一个 Servlet 容器(如 Tomcat、Jetty、Undertow)。
  2. 你编写一个 HttpServlet 类,并重写 doGet, doPost 等方法。
  3. 容器负责接收原始 HTTP 请求,并将其解析成一个 HttpServletRequest 对象。
  4. 容器负责构建一个 HttpServletResponse 对象。
  5. 容器调用你的 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) 作为基础,并结合 OkHttpApache HttpClient 来调用外部服务。
  • 如果你想面试或深入理解网络编程:尝试用 ServerSocket 手动实现一个简单的 HTTP 服务器,这个经历会让你对 HTTP 协议有质的飞跃。
分享:
扫描分享到社交APP
上一篇
下一篇