杰瑞科技汇

Java Socket 如何实现 HTTP 请求?

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

Java Socket 如何实现 HTTP 请求?-图1
(图片来源网络,侵删)

核心思想

HTTP 协议是构建在 TCP 协议之上的应用层协议,使用 Java Socket 实现 HTTP 的核心步骤如下:

  1. 建立 TCP 连接:使用 java.net.Socket 类与目标服务器的指定端口(通常是 80 或 443)建立连接。
  2. 构造 HTTP 请求:按照 HTTP 协议的格式,构建一个请求字符串,这包括请求行(如 GET / HTTP/1.1)、请求头(如 Host: www.example.com)和可选的请求体(GET 请求通常没有)。
  3. 发送请求:通过 Socket 获取输出流,将构造好的 HTTP 请求字符串发送给服务器。
  4. 接收响应:通过 Socket 获取输入流,读取服务器返回的 HTTP 响应,响应同样由响应行、响应头和响应体组成。
  5. 解析响应:将输入流中的数据解析成我们可以理解的格式,例如提取状态码、响应头和内容。
  6. 关闭连接:操作完成后,关闭 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 和流,确保资源被正确释放。

Java Socket 如何实现 HTTP 请求?-图2
(图片来源网络,侵删)
// 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.1
    • GET 是方法。
    • 是请求的资源路径。
    • 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();

发送请求

通过 SocketgetOutputStream() 获取输出流,并将 httpRequest 字符串写入。

// 在 try-with-resources 块内部
// 2. 发送请求
OutputStream out = socket.getOutputStream();
out.write(httpRequest.getBytes());
out.flush(); // 确保所有数据都被发送
System.out.println("--- Request Sent ---");
System.out.println(httpRequest);

接收并解析响应

服务器返回的响应格式如下:

Java Socket 如何实现 HTTP 请求?-图3
(图片来源网络,侵删)
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.");
分享:
扫描分享到社交APP
上一篇
下一篇