今天总是和昨天不一样,所以很珍贵。
——《玉子爱情故事》
java.net包
java提供了一些标准库来处理网络通信,最常用的就是 java.ne
t包,它提供了对网络操作的支持。
其中常用的类有:
URL
:用来表示一个URL地址(例如:http://blog.ulna520.top)
HttpURLConnection
:用来处理HTTP请求(GET,POST等)
Socket
:用于创建客户端与服务器之间的TCP连接,适用于低层次的网络编程。
URLConnection
:是 HttpURLConnection
的父类,可以用来发送和接收数据。
发送HTTP 请求的步骤
创建URL对象
URL对象负责解析链接中的协议、主机地址、端口号、文件路径、查询字符串等信息。提供后续网络连接时的支持。
使用URL类需要导入:
常用的构造方法如下:
直接传入完整地址:
1
| URL url = new URL("http://www.example.com/index.html");
|
根据协议、主机名、端口号和文件路径创建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 }
|
创建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;
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 类进行转化,需要导入包:
1
| import com.fasterxml.jackson.databind.ObjectMapper;
|
具体过程如下:
1 2 3 4 5 6 7 8 9
| Post post = new Post(); post.setTitle("foo"); post.setBody("bar"); post.setUserId(1);
ObjectMapper objectMapper = new ObjectMapper(); String json = objectMapper.writeValueAsString(post);
|
方法 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 = new Post(); post.setTitle("foo"); post.setBody("bar"); post.setUserId(1);
Gson gson = new Gson(); String json = gson.toJson(post);
|
创建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"); connection.setRequestMethod("POST"); connection.setRequestMethod("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
| 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) { 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; 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 client = HttpClient.newHttpClient();
|
使用方法链创建对象:
1 2 3 4
| HttpClient client = HttpClient.newBuilder() .version(HttpClient.Version.HTTP_2) .build();
|
大多数情况下默认的对象即可。
构造GET请求
使用方法链的形式构建:
1 2 3 4
| HttpRequest request = HttpRequest.newBuilder() .uri(uri) .GET() .build();
|
构造POST请求
1 2 3 4 5 6 7 8
| HttpRequest request = HttpRequest.newBuilder() .uri(uri) .header("Content-Type", "application/json") .header("Authorization", "Bearer some_token_here") .header("User-Agent", "MyApp/1.0") .POST(HttpRequest.BodyPublishers.ofString(json)) .build();
|
- 可以通过调用多个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()); });
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()
:会等待整个响应结束后一次返回数据。