我试图理解使用HttpURLConnection
类(或HttpsURLConnection
类)在Java中执行HTTP请求的逻辑。下面是我执行GET请求并将所有响应有效负载打印到标准输出的代码。
URL url = new URI("https://swapi.dev/api/planets/1/");
String line;
HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("Accept", "application/json");
BufferedReader input = new BufferedReader(
new InputStreamReader(connection.getInputStream())
);
// read all data from response payload & print to screen
while ((line=input.readLine()) != null) {
System.out.println(line);
}
System.out.println("All data retrieved!");
问题来了:这段代码工作得很好,成功地读取了所有信息并完成了运行!我的问题是,为什么执行最终不是在while循环的input.readLine()
部分被阻塞?
当我在TCP级别上执行类似的while-loop读取时,这样的readLine
将阻塞,直到从客户端关闭连接。根据HttpURLConnection
类的文档(这里),底层TCP连接通常不会因为性能原因而关闭。因此,如果我想象connection.getInputStream()
与TCP模拟中的socket.getInputStream()
相同,我预计input.readLine()
调用最终也会阻塞。
我猜这个方法可能会在HttpURLConnection
的情况下被覆盖的某个地方(也许利用Content-Length
头属性?)但是,我在任何地方都找不到这个覆盖。
由于我试图尽可能深入,如果您能帮助准确定位HttpURLConnection
类如何导致我的while循环最终结束,那将是伟大的。
为什么在while循环的
input.readLine()
部分最终没有执行阻塞?
因为当Reader.readLine()
调用到达它正在读取的输入流的流尾时,它将返回null
。
当服务器端发送完响应数据时,它将到达客户端的流结束…客户端已经全部读过了。
流的结尾可能与服务器关闭TCP/IP连接对应,也可能不对应。如果HTTP块传输编码用于HTTP连接,则不会。当客户端和服务器同意对多个请求/响应使用单个连接时,使用分块编码。块头将告诉客户端,当它已经到达一个文档的结尾…并且它应该在输入流上发出流结束信号。
content-length
报头可能会也可能不会出现在1中。但是请记住,服务器可以在没有content-length
头的情况下发送响应…这意味着客户端不会提前知道需要多少数据,并且无法将其用于"框架";响应体。
详细信息请参考指定HTTP的rfc。(这比查看客户端Java源代码要好。客户端代码只告诉了你故事的一半。)
1 - HTTP规范规定,如果服务器在响应头中包含content-length
,它必须准确地发送该字节数。但是,如果服务器发送的字节比它说的少或多,它不会强制任何特定的客户端行为。