Python SSL - 当数据大于 1400 时偏移量



我正在使用 Python 中的 ssl 模块,并且在似乎是缓冲区时遇到了一个小问题。

我有以下过程来处理来自套接字的数据,并且我还基于此问题使用 pending 添加了一个 while 循环,但它并没有解决问题。我也过大了缓冲区无济于事。

RECV_BUFFER = 131072
def handle(client_socket):
try:
rxdata = client_socket.recv(RECV_BUFFER)
if rxdata:
print("Rx: " + rxdata.decode())
while(client_socket.pending()):
rxdata = client_socket.recv(RECV_BUFFER)
sys.stdout.write(rxdata.decode())
except Exception as e:
print("Exception: " + str(e))

出于测试目的,我设置了一个用户输入,以便可以直接测试。GET /返回"Hello World",而GET /other返回长字符串。每次缓冲区溢出时,返回都会偏移 1,如下所示。

Command>GET /
Tx: GET /
Rx: HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
X-Cloud-Trace-Context: a65f614b75674fa723b7d69c1af03a0e;o=1
Date: Sun, 02 Sep 2018 16:00:19 GMT
Server: My Frontend
Content-Length: 12
Hello World!
Command>GET /other
Tx: GET /other
Rx: HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
X-Cloud-Trace-Context: 90033f7e308e07508106359c3e7c76d1
Date: Sun, 02 Sep 2018 16:00:23 GMT
Server: My Frontend
Content-Length: 1924
This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. T
Command>GET /
Tx: GET /
Rx: his is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. This is something else. End.
Command>GET /other
Tx: GET /other
Rx: HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
X-Cloud-Trace-Context: 160b0cd5f80982bf1e7ab7dd5d94996d
Date: Sun, 02 Sep 2018 16:00:26 GMT
Server: My Frontend
Content-Length: 12
Hello World!

这是怎么回事,应该如何解决?

我不完全确定您要做什么,但我认为您的服务器基本上是这样工作的:

  1. 阅读命令(一行(。
  2. 立即发送完整响应。

鉴于您正在使用仅检查SSL套接字中是否仍有解密数据的pending我的猜测是您假设如果数据由服务器以单个sent发送,那么它也将被客户端立即读取。但事实并非如此。这里实际发生的事情是这样的:

  1. 服务器每天发送很多,比如 20000 字节。
  2. 在 SSL 级别,这些至少是两个 SSL 记录,因为单个记录的大小只能为 16384。因此,假设它将执行 16384 的记录和其余记录(3616 字节(。
  3. ssl_socket.revc(RECV_BUFFER)至少会从底层 TCP 连接读取尽可能多的数据,因为它需要具有完整的 SSL 记录。然后它将解密SSL记录并返回最多RECV_BUFFER字节的解密数据。
  4. ssl_socket.pending()会告诉您 SSL 套接字中是否仍有未读解密数据。它不会检查底层 TCP 套接字上是否有可用数据。如果 SSL 套接字中仍有数据,则下一个ssl_socket.recv(...)将从这些数据返回,但不会尝试从底层 TCP 套接字读取更多数据。仅当SSL套接字中没有更多解密但未读的数据可用时,recv才会从底层TCP套接字中读取更多数据 - 但在这种情况下,pending将返回false,因此您将永远不会尝试读取更多数据。

这意味着可能只读取和解密第一条SSL记录,并在recv内返回。因此,如果您发送下一个命令,您将不会获得新的响应,但您实际上将从上一个请求中读取剩余的响应数据。

为了修复代码,您需要修复您的假设:SSL需要被视为数据流,而不是消息协议(TCP也是如此(。这意味着您不能假设消息被完整读取并且将完整返回,或者至少在 SSL 对象中已经完整返回。相反,您要么需要预先知道响应的大小(例如在响应前面加上长度(,要么需要有一些明确的标记来表示响应已经结束并读取到此标记。

这是我确定的最终解决方案。我觉得这是一个比之前发布的更正确的解决方案。它还具有通过指定True作为第二个参数来去除标头或保留原位的选项:

def handle(client_socket, raw=False):
data = client_socket.recv()
reCL = re.search('Content-Length: (d+)', data.decode(), re.MULTILINE)
contentLength = int(reCL.group(1))
contentLengthEndChar = reCL.end()+4
dataSize = contentLength 
if raw == True: dataSize += contentLengthEndChar
sslRecordPending = math.ceil(dataSize / 16384) - 1 #SSL records left; not used
socket_active = True
rxdata = b''
if raw == True: rxdata = data[:contentLengthEndChar]
rxdata += data[contentLengthEndChar:]
while True:
try:
if len(rxdata) == dataSize: break
rxdata += client_socket.recv()
except socket.timeout:
break
return rxdata.decode()

最新更新