返回

Delphi Android SSL 会话恢复失效问题详解与解决方案

Android

Delphi Android 应用中 SSL 会话恢复失效问题解决

开发 Delphi 应用时,如果客户端需要编译到 Android 和 Windows 平台,并且通过 HTTPS 与 IIS 上的 Delphi Datasnap ISAPI DLL 通信,可能会遇到一个棘手的问题:在 Android 环境下,SSL 会话无法正确恢复。

具体表现是,每次 HTTPS 请求都会使用一个新的端口号,并进行完整的 SSL 握手。通过 Wireshark 抓包可以看到:每个请求都使用了不同的端口(如 55467, 580127, 61099 等)。这样会导致程序响应缓慢,甚至崩溃。

但在 Windows 环境下,或者在 Android 环境下使用 Indy 组件时,一切正常。只有一个端口被使用,并进行了正确的 SSL 会话恢复。

问题原因分析

问题的根源在于 Delphi 的 System.Net.HttpClient.Android.pas 单元。这个单元负责处理 Android 平台下的 HTTP/HTTPS 请求。 观察发现的现象推测,问题可能出在以下几个方面:

  1. 连接池管理不当: TNetHTTPClient (或者说底层的 Android 网络库) 可能没有正确管理连接池。每次请求都创建了新的连接,而不是复用已有的连接。

  2. SSL 会话 ID 处理问题: SSL 会话恢复依赖于客户端和服务器之间对会话 ID 的正确处理。System.Net.HttpClient.Android.pas 可能没有正确存储或发送 SSL 会话 ID。

  3. 底层网络库的 Bug 或限制: Delphi 在 Android 平台上使用的底层网络库(可能是 Java 的 HttpsURLConnection 或其他)可能存在 Bug,或者对 SSL 会话恢复的支持不完善。

  4. Datasnap 特定的问题: Datasnap 本身也可能和底层连接池处理逻辑存在一些交互问题,导致了Android客户端的处理逻辑不正确。

解决方案

下面针对以上可能的原因,分条列出一些可行的解决方案。

1. 显式控制 TNetHTTPClient 的生命周期

默认情况下,每次请求时都会创建 TNetHTTPClient, 会导致无法复用连接. 可以尝试全局持有, 重复使用。

  • 原理: 通过显式创建和释放 TNetHTTPClient 对象,尝试确保在多个请求之间重用同一个连接。

  • 代码示例:

    var
      GlobalNetHTTPClient: TNetHTTPClient;
    
    procedure TMyForm.FormCreate(Sender: TObject);
    begin
      GlobalNetHTTPClient := TNetHTTPClient.Create(nil);
      // 设置其他属性,例如超时时间等
    end;
    
    procedure TMyForm.FormDestroy(Sender: TObject);
    begin
      GlobalNetHTTPClient.Free;
    end;
    
    procedure TMyForm.SendRequest;
    var
      Response: IHTTPResponse;
    begin
        Response := GlobalNetHTTPClient.Get('https://xxxx');
        // 处理响应...
    end;
    

    进阶技巧 : 可配合 Connection: keep-alive 头使用。

    GlobalNetHTTPClient.Connection := 'keep-alive';
    

2. 检查并手动处理 SSL 会话 (不太可能有效,仅作为理论探讨)

  • 原理: 如果确认问题是由于 System.Net.HttpClient.Android.pas 没有正确处理 SSL 会话 ID,可以尝试手动获取和设置 SSL 会话 ID。 但这通常不是推荐的做法,因为这涉及到对底层 SSL/TLS 协议的深入理解,并且可能会因为 Delphi 版本的不同而失效。 另外,这需要修改 System.Net.HttpClient.Android.pas

    • 步骤简述(不建议尝试)
      1. 修改System.Net.HttpClient.Android.pas,在执行请求前, 尝试手动获取 SSLSession.
      2. 获取其ID,并尝试手动在下次请求设置.
  • 代码示例: (仅为概念性示例,无法直接运行)

    // 以下代码为伪代码,无法直接使用,仅用于说明思路
    // 需要深入修改 System.Net.HttpClient.Android.pas
    
    // 获取 SSL 会话 ID (假设有 GetSSLSessionID 函数)
    SessionID := GetSSLSessionID(NetHTTPRequest1.HttpClient);
    
    // 在下一次请求时,设置 SSL 会话 ID (假设有 SetSSLSessionID 函数)
    SetSSLSessionID(NetHTTPRequest1.HttpClient, SessionID);
    
    NetHTTPRequest1.Get('https://xxxx');
    
  • ** 安全建议** : 修改标准库是高风险操作,应备份文件,且做充分测试。

3. 使用 Indy 组件 (TIdHTTP)

  • 原理: Indy (Internet Direct) 是一个流行的 Delphi 网络组件库,它提供了 TIdHTTP 组件,可以用于发送 HTTP/HTTPS 请求。 既然测试证明在Android环境下Indy工作正常,则使用它是目前已知能规避此问题的直接有效的方法。

  • 代码示例: (已在问题中提供,这里再次列出)

    var
      Response: string;
      IdHTTP: TIdHTTP;
      i: integer;
    begin
        IdHTTP := TIdHTTP.Create(nil);
      try
        for i := 1 to 3 do
        begin
           try
            Response := IdHTTP.Get('https://xxxxxxxxxx');
           except
            on E: Exception do
            begin
               //错误处理
            end;
           end;
        end;
      finally
          IdHTTP.Free;
      end;
    end;
    

    进阶技巧 :
    可设置 TIdSSLIOHandlerSocketOpenSSLSSLOptions.ModesslmClient, SSLOptions.Method 根据服务端支持选择,如sslvTLSv1_2.

     var
      IdSSLIOHandler: TIdSSLIOHandlerSocketOpenSSL;
     begin
       IdSSLIOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(IdHTTP);
       IdHTTP.IOHandler := IdSSLIOHandler;
       IdSSLIOHandler.SSLOptions.Mode := sslmClient;
       IdSSLIOHandler.SSLOptions.Method := sslvTLSv1_2; // 或其他支持的方法
       // ... 其他设置,如证书等 ...
     end;
    
    

4. 更换/更新 Delphi 版本(如果可能)

  • 原理: 新版本的 Delphi 可能会修复 System.Net.HttpClient.Android.pas 中的 Bug,或者改进对 SSL 会话恢复的支持。如果条件允许(有新版License, 且兼容现有项目),可以尝试升级 Delphi 版本。

5. 使用 Fiddler 或 Charles Proxy 进一步调试

  • 原理: Fiddler 和 Charles Proxy 都是强大的 HTTP/HTTPS 调试工具。可以使用它们来捕获 Android 设备上的网络流量,更详细地分析 SSL 握手过程,以确定问题是否出在客户端。
    尤其适合检查会话 ID 的收发是否正确。
  • 操作步骤:
    1. 在电脑上安装 Fiddler 或 Charles Proxy。
    2. 配置 Fiddler 或 Charles Proxy 以捕获 HTTPS 流量(需要安装证书)。
    3. 将 Android 设备连接到电脑,并配置代理服务器。
    4. 在 Android 设备上运行 Delphi 应用程序,并使用 Fiddler 或 Charles Proxy 观察网络流量。
    5. 检查每个SSL握手是否有 session id.

6. (针对 Datasnap) 研究 Datasnap 的 KeepAlive 机制.

  • 原理 : 有时 Datasnap 自身的连接维持可能影响了底层HTTPClient 的复用. 检查 Server/Client 两端的 KeepAlive 相关设置。

  • 相关属性和方法(Datasnap):

    • DSHTTPWebDispatcher.KeepAlive (服务器端)
    • TSQLConnection.KeepAlive (客户端,通过设置 SQLConnection 来控制 Datasnap客户端连接)
    • DSHTTPService.KeepAlive (服务端)
    • Datasnap通道(TDSTunnelSession)也可能和生命周期维护有关.
      进行测试修改, 来确定影响.
  • 注意: 在设置KeepAlive属性时,需要客户端与服务端配合。

7.联系 Embarcadero 技术支持

  • 原理: 如果以上方法都无法解决问题,可能是 Delphi 本身的 Bug 或限制。可以联系 Embarcadero 技术支持,提供详细的问题和复现步骤,寻求官方帮助。

总结

在处理 Delphi Android 应用中 SSL 会话恢复问题时,优先使用 Indy 方案是最可靠,简便的. 若条件不允许, 再依次考虑手动维护连接,调试 Datasnap 本身的连接策略. 更新 Delphi版本或联系官方通常是最后的手段。 通过系统调试手段(Wireshark, Fiddler等), 能更快定位原因.