Java网络编程入门-HTTP请求

今天总是和昨天不一样,所以很珍贵。

——《玉子爱情故事》

java.net包

java提供了一些标准库来处理网络通信,最常用的就是 java.net包,它提供了对网络操作的支持。

其中常用的类有:

  • URL:用来表示一个URL地址(例如:http://blog.ulna520.top)
  • HttpURLConnection:用来处理HTTP请求(GET,POST等)
  • Socket:用于创建客户端与服务器之间的TCP连接,适用于低层次的网络编程。
  • URLConnection:是 HttpURLConnection 的父类,可以用来发送和接收数据。

发送HTTP 请求的步骤

创建URL对象

URL对象负责解析链接中的协议、主机地址、端口号、文件路径、查询字符串等信息。提供后续网络连接时的支持。

使用URL类需要导入:

1
import java.net.URL;

常用的构造方法如下:

  1. 直接传入完整地址:

    1
    URL url = new URL("http://www.example.com/index.html");
  2. 根据协议、主机名、端口号和文件路径创建URL

    1
    URL url = new URL("http", "www.example.com", 80, "/index.html");

URL对象会自动解析连接并填充对应的字段,protocol、host、port等。

封装请求体

在我们发送POST请求时,我们常常要将数据封装为json格式的数据来发送。构造JSON对象通常可以通过以下方式完成:

  • 使用Jackson 库处理POJO类与JSON类的转化
  • 使用Gson 处理POJO类与JSON类的转化

方法 1:使用 Jackson 库构造 JSON 对象(推荐)

Jackson 是 Java 中最常用的 JSON 处理库,它可以将 Java 对象自动转换为 JSON 字符串,也可以将 JSON 字符串反序列化为 Java 对象。

要使用这个包我们需要导入依赖:

1
2
3
4
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>

示例:假设我们要发送一个如下的JSON请求:

1
2
3
4
5
6
{
"title": "foo",
"body": "bar",
"userId": 1
}

  1. 创建POJO类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class Post {
    private String title;
    private String body;
    private int userId;

    // Getters and setters
    public String getTitle() {return title;}
    public void setTitle(String title) {this.title = title;}
    public String getBody() {return body;}
    public void setBody(String body) {this.body = body;}
    public int getUserId() {return userId;}
    public void setUserId(int userId) {this.userId = userId;}
    }

  2. 创建对象并转化
    我们使用ObjectMapper 类进行转化,需要导入包:

    1
    import com.fasterxml.jackson.databind.ObjectMapper;

    具体过程如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 创建 POST 请求的对象
    Post post = new Post();
    post.setTitle("foo");
    post.setBody("bar");
    post.setUserId(1);

    // 创建 ObjectMapper 用于将 POJO 转换为 JSON
    ObjectMapper objectMapper = new ObjectMapper();
    String json = objectMapper.writeValueAsString(post); // 将 POJO 转为 JSON 字符串

方法 2:使用 Gson 库构造 JSON 对象

Gson 是一个轻量级的JSON处理库。依赖:

1
2
3
4
5
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.8</version>
</dependency>

使用Gson库构造json数据:

1
2
3
4
5
6
7
8
9
10
11
import com.google.gson.Gson;

// 创建 POST 请求的对象
Post post = new Post();
post.setTitle("foo");
post.setBody("bar");
post.setUserId(1);

// 使用 Gson 将 POJO 转为 JSON 字符串
Gson gson = new Gson();
String json = gson.toJson(post); // 将 POJO 转为 JSON 字符串

创建HttpURLConnection对象

HttpURLConnection 对象用于处理HTTP请求和响应。我们可以通过URL对象的 openConnection()方法获取一个HttpURLConnection对象。

HttpURLConnection 的基本用法:

  • 创建 URL 对象 :首先需要创建一个 URL 对象,表示你想要访问的资源的地址。
  • 打开连接 :使用 URL.openConnection() 方法获取一个 HttpURLConnection 对象。
  • 配置请求方法和头部信息 :设置 HTTP 请求方法(如 GET 或 POST)及其他请求参数。
  • 发送请求并接收响应 :通过连接发送请求并接收响应。
  • 关闭连接 :处理完响应后,关闭连接。

打开连接

1
2
// 打开连接
HttpURLConnection connection = (HttpURLConnection) url.openConnection();

设置请求方法

1
2
3
4
// 设置请求方法
connection.setRequestMethod("GET"); //使用GET方法
connection.setRequestMethod("POST");//使用POST方法
connection.setRequestMethod("PUT"); //使用PUT方法

设置请求头

1
2
con.setRequestProperty("Content-Type", "application/json");
con.setRequestProperty("Authorization", header); //可以设置多个头字段

启动输入输出流(GET无需)

POST请求需要向服务器递交数据(表单或JSON),这些数据通过请求体发送,需要通过:

1
2
// 启用输入输出流
connection.setDoOutput(true);

写入请求体数据:

1
2
3
4
5
6
7
8
// 请求体(JSON 数据)
String jsonInputString = "{\"name\": \"John\", \"age\": 30}";

// 写入请求体数据
try (OutputStream os = connection.getOutputStream()) {
byte[] input = jsonInputString.getBytes("utf-8"); //转化为字节流
os.write(input, 0, input.length); // 发送请求数据
}

接收数据

获取响应码

我们需要通过响应码判断这次请求的状态,常见的状态响应码如下:

  • 200 ok:请求成功,服务器返回所请求的数据。
  • 400 Bad Request :请求无效,服务器无法理解。
  • 401 Unauthorized :未授权,需提供认证信息。
  • 403 Forbidden :服务器拒绝请求,权限不足。
  • 404 Not Found :请求的资源不存在。
  • 405 Method Not Allowed :请求方法不被允许。
  • 408 Request Timeout :请求超时。

获取响应码的方法如下:

1
2
3
// 获取响应码
int responseCode = connection.getResponseCode();
System.out.println("Response Code: " + responseCode);

我们只有接收到200的状态码,我们才可以开始接收数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 读取响应体
if (responseCode == HttpURLConnection.HTTP_OK) { // 200 OK
try (BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
String inputLine;
StringBuilder response = new StringBuilder();

while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
System.out.println(response.toString());
}
} else {
System.out.println("GET/POST request failed");
}

关闭连接

1
2
// 关闭连接
connection.disconnect();

HttpClient (Java11+)

HttpClient 是Java11引入的一个现代化的HTTP客户端,提供更简洁、灵活和抢到的API来进行HTTP通信。它支持同步和异步操作,并内置许多现代化的特性。

导入相关组件:

1
2
3
4
5
import java.net.URI;                    //URI类
import java.net.http.HttpClient; //客户端
import java.net.http.HttpRequest; //构建请求消息
import java.net.http.HttpResponse; //处理响应消息
import java.net.http.HttpHeaders; //处理响应头

使用HttpClient 发送请求之前,创建URL类和准备好请求体的方法仍然不变。

创建HttpClient对象

创建默认方法的HttpClient:

1
2
// 创建 HttpClient 实例
HttpClient client = HttpClient.newHttpClient();

使用方法链创建对象:

1
2
3
4
// 创建 HttpClient
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2) // 使用 HTTP/2
.build();

大多数情况下默认的对象即可。

构造GET请求

使用方法链的形式构建:

1
2
3
4
HttpRequest request = HttpRequest.newBuilder()	//创建一个构建器
.uri(uri) //请求的URL
.GET() //请求的方法
.build(); //构建得到最终对象

构造POST请求

1
2
3
4
5
6
7
8
HttpRequest request = HttpRequest.newBuilder()
.uri(uri) // 设置 URI
.header("Content-Type", "application/json") // 设置 Content-Type 请求头
.header("Authorization", "Bearer some_token_here") // 设置 Authorization 请求头
.header("User-Agent", "MyApp/1.0") // 设置 User-Agent 请求头
.POST(HttpRequest.BodyPublishers.ofString(json)) // 设置 POST 请求体
.build(); // 构建 HttpRequest 对象

  • 可以通过调用多个header()方法加入多个请求头。
  • json为String类型的json格式的数据

同步请求(Synchronous Request)

同步请求是指客户端发送请求后会等待服务器响应,直到收到响应或者超时为止。在同步请求过程中,线程会被阻塞,直到请求完成。

同步请求通过**client.send()**方法实现。

1
2
// 发送请求并获取响应
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
  • HttpResponse.BodyHandlers.ofString() 用于告诉客户端,将响应体解析为字符串。
  • 未得到完整答复响应超时前会程序会阻塞在这一句

打印响应体:

1
2
3
/ 获取响应状态码和响应体
System.out.println("Response Code: " + response.statusCode());
System.out.println("Response Body: " + response.body());

异步请求(Asynchronous Request)

异步请求是指客户端发送请求后,不会等待服务器响应,而是立即继续执行程序中的其他任务。响应会通过 CompletableFuture异步返回,等到响应返回时,可以在回调函数中处理响应。

异步请求通过 client.sendAsync()方法。该方法返回一个 CompletableFuture,它可以在未来的某个时间结点异步完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 异步发送请求
CompletableFuture<HttpResponse<String>> responseFuture = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());

// 处理响应
responseFuture.thenAccept(response -> {
System.out.println("Response Code: " + response.statusCode());
System.out.println("Response Body: " + response.body());
});

// 这里可以继续执行其他操作,而不会被阻塞

// 为了等待异步操作完成,主线程需要稍等一下,通常你可以用 `join()` 来确保主线程等待异步操作
responseFuture.join();

流式传输

在调用大模型时,流式传输可以让我们减少等待的时间,不用等待大模型完全输出,而是可以实现边生成边接收数据。

流式传输时数据包会被分为多个非常小的数据包进行发送,每个数据包中包含了大模型新生成的内容,通常只有一个或两个汉字。不同的服务提供的数据格式可能不同,我们以BigModel的模型为例:

1
2
3
4
5
6
7
data: {"id":"8313807536837492492","created":1706092316,"model":"glm-4-plus","choices":[{"index":0,"delta":{"role":"assistant","content":"土"}}]}
data: {"id":"8313807536837492492","created":1706092316,"model":"glm-4-plus","choices":[{"index":0,"delta":{"role":"assistant","content":"星"}}]}
....
data: {"id":"8313807536837492492","created":1706092316,"model":"glm-4-plus","choices":[{"index":0,"delta":{"role":"assistant","content":","}}]}
data: {"id":"8313807536837492492","created":1706092316,"model":"glm-4-plus","choices":[{"index":0,"delta":{"role":"assistant","content":"主要由"}}]}
data: {"id":"8313807536837492492","created":1706092316,"model":"glm-4-plus","choices":[{"index":0,"finish_reason":"length","delta":{"role":"assistant","content":""}}],"usage":{"prompt_tokens":60,"completion_tokens":100,"total_tokens":160}}
data: [DONE]

而非流式传输时,所有的内容会等到大模型生成所有的内容后,在将消息打包一次性发送过来,通常消息如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"created": 1703487403,
"id": "8239375684858666781",
"model": "glm-4-plus",
"request_id": "8239375684858666781",
"choices": [
{
"finish_reason": "stop",
"index": 0,
"message": {
"content": "以AI绘蓝图 — 智谱AI,让创新的每一刻成为可能。...",
"role": "assistant"
}
}
],
"usage": {
"completion_tokens": 217,
"prompt_tokens": 31,
"total_tokens": 248
}
}

HttpURLConnection

HttpURLConnection接收数据时,我们创建一个BufferedReader类,用于读取数据。

无论是不是流式读取,我们都使用同样的方法:

1
2
3
4
5
6
7
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
System.out.println("Response Body: " + response.toString());

只不过对于流式传输,while循环中的每次读取的inputLine,即为每次收到的一条数据流。

而对于非流式传输,最终得到的 response 才是最终得到的数据。

HttpClient

要使用HttpClient 流式接收数据,示例如下:

1
2
3
4
5
client.send(request, HttpResponse.BodyHandlers.ofLines())
.body()
.forEach(line -> {
System.out.println("Response: " + line);
});

HttpResponse.BodyHandlers.ofLines() :它会在接收到每一行数据时就立即处理。

BodyHandlers.ofString():会等待整个响应结束后一次返回数据。


Java网络编程入门-HTTP请求
http://blog.ulna520.com/2025/05/13/Java网络编程入门_20250513_193851/
Veröffentlicht am
May 13, 2025
Urheberrechtshinweis