TIdHTTP.Get()因TLSv1.3会话票阻塞问题求助
解决方案
针对你遇到的TLS会话票导致TIdHTTP.Get()阻塞的问题,可以尝试以下几种修复方式:
1. 修改TIdSSLIOHandlerSocketOpenSSL.Readable()方法
问题核心是Readable()把TLS层的会话票数据误判为应用层可读数据,导致后续SSL_read()阻塞。可以修改该方法,只在存在应用层数据时返回True:
function TIdSSLIOHandlerSocketOpenSSL.Readable(AMSec: Integer): Boolean; var LState: TSSLState; LBytes: Integer; LBuffer: array[0..0] of Byte; begin Result := inherited Readable(AMSec); if Result then begin // 先检查SSL是否有已解密的应用层数据待读取 if SSL_pending(FSSL) > 0 then Exit; // 非阻塞peek,判断底层数据是否为应用层数据 LState := FSSL.State; FSSL.State := ssl_st_read; try LBytes := SSL_peek(FSSL, @LBuffer, SizeOf(LBuffer)); if LBytes <= 0 then begin // 没有应用层数据,可能是TLS控制帧(如会话票) Result := False; end; finally FSSL.State := LState; end; end; end;
这段代码在确认底层socket可读后,先检查SSL缓冲区是否有现成的应用层数据;如果没有,通过SSL_peek()尝试非阻塞读取,若无法获取应用层数据,则判定为不可读,避免后续触发不必要的SSL_read()。
2. 在HTTP响应处理完成后清理TLS控制帧
在TIdCustomHTTP.DoRequest方法中,当确认所有HTTP响应数据已接收完毕后,添加一段代码处理TLS层的控制数据(如会话票),避免后续Readable()检测到它们:
// 假设在读取完响应内容、处理完响应头之后添加 var LBytes: Integer; LBuffer: array[0..1023] of Byte; begin // 非阻塞读取,消耗TLS控制帧(会话票等) while True do begin LBytes := SSL_read(FSSL.IOHandler.FSSL, @LBuffer, SizeOf(LBuffer)); if LBytes <= 0 then begin LBytes := SSL_get_error(FSSL.IOHandler.FSSL, LBytes); if (LBytes = SSL_ERROR_WANT_READ) or (LBytes = SSL_ERROR_WANT_WRITE) then Break; // 其他错误忽略,或根据情况处理 Break; end; // 读取到的是TLS控制数据,无需处理,直接丢弃 end; end;
这段代码通过非阻塞的SSL_read()消耗掉TLS层的控制数据,确保后续判断KeepAlive时,Readable()不会因为这些数据返回True。
3. 调整KeepAlive判断逻辑,仅依赖HTTP协议头
修改TIdCustomHTTP.DoRequest中判断是否保持连接的逻辑,不再调用Response.KeepAlive触发Readable()检测,而是直接根据HTTP响应的Connection头来决定:
// 替换原来的KeepAlive判断代码 LKeepAlive := (Response.Connection = 'keep-alive') and not FForceClose;
这种方式严格遵循HTTP协议规范,只要响应头指定keep-alive就保持连接,否则关闭,彻底避免因TLS层数据导致的误判。不过需要注意,这种方式可能忽略某些服务器的非常规行为,但符合标准HTTP流程。
内容的提问来源于stack exchange,提问作者SoCalAndy
相关产品推荐
相关产品推荐

