分块传输编码终止符序列和 TCP recv()

Chunked Transfer Encoding Terminator Sequence and TCP recv()

import ssl
import socket

ssl_context = ssl.create_default_context()
target = 'swapi.co' 
port = 443 
resource = '/api/people/1/'
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 

secure_client = ssl_context.wrap_socket(client, server_hostname=target)
send_str = 'GET {} HTTP/1.1\r\nHost: {}:{}\r\n\r\n'.format(resource, target, str(port))

secure_client.connect((target, port))
secure_client.send(send_str.encode()) 
print(send_str)

print(len(secure_client.recv(8192))) # 1282
print(len(secure_client.recv(8192))) # 5. Why?

以上是一个简单的 Python 程序,它使用 TCP 套接字向 Star Wars API 发送 HTTP 请求。

这是发送的请求:

GET /api/people/1/ HTTP/1.1
Host: swapi.co:443

响应 header 中有 Transfer-Encoding: chunked。当执行第一个 recv 时 header 并获得第一个块。但是,要获取带有 终止符序列 ("0\r\n\r\n") 的最后一个块,必须调用第二个 recv。这种行为的根本原因是什么?

这是因为在分块传输编码中,数据流被分成一系列non-overlapping"chunks"。这些块相互独立地发送和接收。

TCP 是一种提供字节流的协议。它没有提供任何方式将 "glue" 个字节一起放入消息中。当您调用 recv 时您将收到的实际字节数是任意的,并且取决于各种不同的因素,例如另一方的确切实现,您调用 recv 的速度,网络的最大消息大小,等等。没有任何意义。

由于您在查询中表示支持 HTTP 1.1 版,因此允许服务器使用 HTTP 1.1 客户端需要支持的任何编码。这包括这种形式的分块编码,它使用一个或多个 "chunks" 数据,每个数据前面都有一个大小指示符。这对于输出由脚本生成并且服务器在生成整个响应之前不知道它有多大的情况很方便。此编码方案允许立即开始发送。

不要在 HTTP 查询中声称符合 HTTP 1.1,除非您的代码支持 HTTP 1.1 标准所说的客户端 "MUST" 支持的所有内容。