You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

启用TLS的Indy TIdHTTPServer无法被客户端连接问题排查求助

问题描述

我正在编写一个Delphi 10.4 Win32测试应用,使用Delphi 10.4自带的Indy 10.6.2.0中的TIdHTTPServer和TIdServerIOHandlerSSLOpenSSL组件,占用本地端口实现C# (.NET 5)客户端与该Win32 Delphi VCL服务端的通信。
我对TLS、SSL和证书了解较少,在开发过程中正在逐步学习,参考的示例让我原本以为这个需求实现起来会很简单。
我使用的.pfx证书属性的“常规”页显示“启用该证书的所有用途”。

我已经引入了OpenSSL 1.0.2.17的DLL文件:

libeay32.dll
ssleay32.dll

这些DLL都可以正常加载。

问题现象

目前客户端无法连接到服务端,.NET侧报错“SSL连接无法建立”。
WireShark抓包显示存在“Client Hello”报文(TLS 1.2协议,携带约20种加密套件),但没有返回“Server Hello”、告警或错误报文,实际上仅存在客户端发送的这一条TLS 1.2报文。
客户端携带的加密套件示例:

TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:(其他若干加密套件)

我尝试将上述完整加密套件列表赋值给TIdHTTPServer的CipherList属性,但收到“SetCipherList failed”错误。如果留空加密套件列表,组件内部会默认使用"AES:ALL:!aNULL:!eNULL:+RC4:@STRENGTH",我就保持了该默认配置。

配置与代码

组件配置

object srv: TIdHTTPServer
  Bindings = <
    item
      IP = '127.0.0.1'
      Port = 5500
    end>
  DefaultPort = 5500
  IOHandler = iohSSL
  MaxConnections = 25
  AutoStartSession = True
  KeepAlive = True
  SessionState = True
  OnCreatePostStream = srvCreatePostStream
  OnDoneWithPostStream = srvDoneWithPostStream
  OnCommandGet = srvCommandGet
  Left = 183
  Top = 118
end
object iohSSL: TIdServerIOHandlerSSLOpenSSL
  SSLOptions.CertFile = 'C:\Users\Mike\AppData\Local\Somewhere\cert.pfx'
  SSLOptions.KeyFile = 'C:\Users\Mike\AppData\Local\Somewhere\cert.pfx'
  SSLOptions.Method = sslvTLSv1_2
  SSLOptions.SSLVersions = [sslvTLSv1_2]
  SSLOptions.Mode = sslmServer
  OnGetPasswordEx = iohSSLGetPasswordEx
  Left = 256
  Top = 120
end

业务代码

procedure TdmCloud.DataModuleCreate(Sender: TObject);
begin
  iohSSL.SSLOptions.CertFile:='.\cert.pfx';
  //iohSSL.SSLOptions.RootCertFile:='.\root2.pem';
  //iohSSL.SSLOptions.KeyFile:='.\key.pem';

  srv.Active:=true;
end;

procedure TdmCloud.iohSSLGetPasswordEx(ASender: TObject; var VPassword: string;
  const AIsWrite: Boolean);
begin
  VPassword := 'password';
end;

procedure TdmCloud.srvCommandGet(AContext: TIdContext;
  ARequestInfo: TIdHTTPRequestInfo;
  AResponseInfo: TIdHTTPResponseInfo);
begin
  AResponseInfo.ResponseNo := HTTP_OK;
  AResponseInfo.ContentType := 'text/html';
  AResponseInfo.ContentText := '<html><head><title>Title</title></head><body>HELLO</body></html>';
end;

procedure TdmCloud.srvCreatePostStream(AContext: TIdContext;
  AHeaders: TIdHeaderList; var VPostStream: TStream);
begin
  VPostStream := TMemoryStream.Create;
end;

procedure TdmCloud.srvDoneWithPostStream(AContext: TIdContext;
  ARequestInfo: TIdHTTPRequestInfo; var VCanFree: Boolean);
begin
  VCanFree := true;
end;

调试排查情况

调试时发现证书文件可以通过事件回调提供的密码正常加载,监听线程在收到请求前运行正常。
TIdHTTPServer的OnCommandGet事件不会触发。调试中发现InternalReadLn()返回看起来是加密后的二进制数据,后续抛出“解析命令出错”的异常,从返回的数据来看这个报错符合预期。
相关代码片段:

var
  i: integer;
  s, LInputLine, LRawHTTPCommand, LCmd, LContentType, LAuthType: String;
  LURI: TIdURI;
  LContinueProcessing, LCloseConnection: Boolean;
  LConn: TIdTCPConnection;
  LEncoding: IIdTextEncoding;
begin
  LContinueProcessing := True;
  Result := False;
  LCloseConnection := not KeepAlive;
  try
    try
      LConn := AContext.Connection;
      repeat
        // InternalReadLn( ) 返回看起来加密的内容,明确是二进制数据
        LInputLine := InternalReadLn(LConn.IOHandler);
        i := RPos(' ', LInputLine, -1);    {Do not Localize}
        if i = 0 then begin   // 该条件成立
          raise EIdHTTPErrorParsingCommand.Create(RSHTTPErrorParsingCommand);
        end;

实际上InternalReadLn()调用的是TIdServerIOHandlerSSLOpenSSL的Readln()方法,返回了上述二进制数据。

相关技术解释提到:

该错误实际来自下面几行代码:

i := RPos(' ', LInputLine, -1); {Do not Localize}
if i = 0 then begin
  raise EIdHTTPErrorParsingCommand.Create(RSHTTPErrorParsingCommand);
end;

这意味着你的客户端发送了格式错误的HTTP请求。每个请求的首行都必须以HTTP版本号结尾,比如:GET /login.html HTTP/1.0,所以才会调用RPos()方法。该错误说明该行内容中不存在空格字符。

虽然确实有可能是请求格式错误,但我使用的客户端请求已经经过测试确认是有效可用的。我尝试用浏览器直接访问https://localhost:5500/,也出现了同样的问题。
这让我得出结论:收到“Client Hello”后TLS协商根本没有启动,我不知道原因是什么,也不清楚Indy中哪个部分负责处理TLS协商流程。
我目前主要怀疑以下几个点:

  • .pfx证书是否缺少什么必要内容?哪怕别人告诉我这是正常的证书,其属性中已经勾选了“服务器身份验证”选项。
  • 要让TLS 1.2正常工作,我需要提供CertFile、RootCertFile、KeyFile、CipherList中的哪些配置?应该怎么选择合适的配置?
  • Delphi 10.4 update 2自带的Indy SSL组件是否存在缺陷,或者和OpenSSL 1.0.2q不兼容?

问题解答

你遇到的核心问题是Indy的TLS协商逻辑没有被触发,IOHandler直接把加密的TLS报文当成明文HTTP内容解析,才会出现解析命令失败的错误,原因是你没有启用TIdHTTPServer的HTTPS支持开关。

具体修复步骤如下:

  1. 开启HTTPS模式:在TIdHTTPServer的属性中将UseTLS属性设置为utUseImplicitTLS,这个属性是控制服务端是否在连接建立后直接启动TLS协商的开关,你没有配置它的话,服务端默认会认为所有入站流量都是明文HTTP,不会触发TLS握手逻辑。
  2. 证书配置修正:你使用PFX格式证书的话,不需要额外拆分证书和密钥文件,只需要将PFX路径同时赋值给CertFile和KeyFile即可,非双向验证场景不需要填写RootCertFile。
  3. 加密套件配置:OpenSSL 1.0.2版本默认的加密套件列表已经支持大部分TLS1.2标准套件,不需要手动设置。你之前设置报错是因为直接用了TLS标准的套件名称字符串,而OpenSSL有自己的套件命名规则,保持默认即可。
  4. 版本兼容性验证:Delphi 10.4自带的Indy 10.6.2完全兼容OpenSSL 1.0.2系列版本,不需要更换版本。

修改完UseTLS属性后,服务端就会在新连接建立后先执行TLS握手,握手成功后再将解密后的HTTP内容交给上层逻辑处理,就能正常触发OnCommandGet事件了。


内容的提问来源于stack exchange,提问作者Mikey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 14:15:05