今天总是和昨天不一样,所以很珍贵。
——《玉子爱情故事》
java.net包
java提供了一些标准库来处理网络通信,最常用的就是 java.net包,它提供了对网络操作的支持。
其中常用的类有:
URL:用来表示一个URL地址(例如:http://blog.ulna520.top)HttpURLConnection:用来处理HTTP请求(GET,POST等)Socket:用于创建客户端与服务器之间的TCP连接,适用于低层次的网络编程。URLConnection:是HttpURLConnection的父类,可以用来发送和接收数据。
发送HTTP 请求的步骤
创建URL对象
URL对象负责解析链接中的协议、主机地址、端口号、文件路径、查询字符串等信息。提供后续网络连接时的支持。
使用URL类需要导入:
import java.net.URL;
常用的构造方法如下:
-
直接传入完整地址:
URL url = new URL("http://www.example.com/index.html"); -
根据协议、主机名、端口号和文件路径创建URL
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 对象。
要使用这个包我们需要导入依赖:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
示例:假设我们要发送一个如下的JSON请求:
{
"title": "foo",
"body": "bar",
"userId": 1
}
-
创建POJO类
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;} } -
创建对象并转化 我们使用ObjectMapper 类进行转化,需要导入包:
import com.fasterxml.jackson.databind.ObjectMapper;具体过程如下:
// 创建 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处理库。依赖:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.8</version>
</dependency>
使用Gson库构造json数据:
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)及其他请求参数。
- 发送请求并接收响应 :通过连接发送请求并接收响应。
- 关闭连接 :处理完响应后,关闭连接。
打开连接
// 打开连接
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
设置请求方法
// 设置请求方法
connection.setRequestMethod("GET"); //使用GET方法
connection.setRequestMethod("POST");//使用POST方法
connection.setRequestMethod("PUT"); //使用PUT方法
设置请求头
con.setRequestProperty("Content-Type", "application/json");
con.setRequestProperty("Authorization", header); //可以设置多个头字段
启动输入输出流(GET无需)
POST请求需要向服务器递交数据(表单或JSON),这些数据通过请求体发送,需要通过:
// 启用输入输出流
connection.setDoOutput(true);
写入请求体数据:
// 请求体(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 :请求超时。
获取响应码的方法如下:
// 获取响应码
int responseCode = connection.getResponseCode();
System.out.println("Response Code: " + responseCode);
我们只有接收到200的状态码,我们才可以开始接收数据:
// 读取响应体
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");
}
关闭连接
// 关闭连接
connection.disconnect();
HttpClient (Java11+)
HttpClient 是Java11引入的一个现代化的HTTP客户端,提供更简洁、灵活和抢到的API来进行HTTP通信。它支持同步和异步操作,并内置许多现代化的特性。
导入相关组件:
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:
// 创建 HttpClient 实例
HttpClient client = HttpClient.newHttpClient();
使用方法链创建对象:
// 创建 HttpClient
HttpClient client = HttpClient.newBuilder()
.version(HttpClient.Version.HTTP_2) // 使用 HTTP/2
.build();
大多数情况下默认的对象即可。
构造GET请求
使用方法链的形式构建:
HttpRequest request = HttpRequest.newBuilder() //创建一个构建器
.uri(uri) //请求的URL
.GET() //请求的方法
.build(); //构建得到最终对象
构造POST请求
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()**方法实现。
// 发送请求并获取响应
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
HttpResponse.BodyHandlers.ofString()用于告诉客户端,将响应体解析为字符串。- 在未得到完整答复或响应超时前会程序会阻塞在这一句
打印响应体:
/ 获取响应状态码和响应体
System.out.println("Response Code: " + response.statusCode());
System.out.println("Response Body: " + response.body());
异步请求(Asynchronous Request)
异步请求是指客户端发送请求后,不会等待服务器响应,而是立即继续执行程序中的其他任务。响应会通过 CompletableFuture异步返回,等到响应返回时,可以在回调函数中处理响应。
异步请求通过 client.sendAsync()方法。该方法返回一个 CompletableFuture,它可以在未来的某个时间结点异步完成。
// 异步发送请求
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的模型为例:
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]
而非流式传输时,所有的内容会等到大模型生成所有的内容后,在将消息打包一次性发送过来,通常消息如下:
{
"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类,用于读取数据。
无论是不是流式读取,我们都使用同样的方法:
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 流式接收数据,示例如下:
client.send(request, HttpResponse.BodyHandlers.ofLines())
.body()
.forEach(line -> {
System.out.println("Response: " + line);
});
HttpResponse.BodyHandlers.ofLines() :它会在接收到每一行数据时就立即处理。
BodyHandlers.ofString():会等待整个响应结束后一次返回数据。