TSynHttpClient 工业级 HTTP/HTTPS 封装库
📖 1. 模块简介
TSynHttpClient 是基于 Ararat Synapse 网络库二次封装的高性能、跨版本的 HTTP/HTTPS 通讯类,针对Delphi做了一系列深度优化。
🌟 核心特性:
- 跨版本完美兼容:利用
{$IFDEF UNICODE}宏,底层自动抹平 Delphi 7 以上的版本差异,彻底杜绝中文与 JSON 乱码。 - 无视系统底层限制:摆脱 Delphi 自带 WinInet 对 TLS 1.2/1.3 的限制,在 Windows XP 到 Win11 上完美握手现代 HTTPS 接口。
- 强力防卡死机制:底层强制干预 TCP 建连与读写超时,彻底解决 DNS 解析阻塞或网络断开导致的主线程假死。
- 智能 SNI 注入:针对多租户环境,自动注入 HTTPS SNI 域名,防止网关返回 500 或 403 误杀。
- 极致复用 (Keep-Alive):支持真正的 TCP/TLS 长连接复用,在批量数据上传场景下,可免除反复握手,性能提升高达 10 倍。
- 自动异常留痕:内置自动日志引擎,遇到 4xx、5xx 或网络底层的 Socket 错误时,自动保存详尽的报文 Dump,极大降低排错难度。
- 2个dll打天下:本项目内置的
libcrypto-1_1.dll和libssl-1_1.dll,可以满足 99% 以上的 HTTPS API 接口调用需求。无需像老的 TIdHTTP 那样区分版本。 - 真实的超时时间控制:区别于TIDHTTP以及Windows系统内置的组件,TSynHttpClient有真实有效的超时时间。
⚙️ 2. 核心依赖与环境配置
本组件的 HTTPS 核心加密能力由 OpenSSL 1.1.1 提供。在使用前,必须处理好动态库依赖。
📦 2.1 必须的 DLL 文件
在发布或运行程序时,必须确保环境中有以下两个 32 位(x86)的动态链接库(非HTTPS请求不需要这两个DLL):
libcrypto-1_1.dll(提供基础密码学算法)libssl-1_1.dll(提供 TLS 1.2 / 1.3 协议栈)
(建议使用采用 /MT 静态编译的 Win32 XP 兼容版,单文件大小在 2.5MB 左右,能免除对 VC++ 运行库的依赖。)
🔍 2.2 Synapse 自动搜索路径规则
Synapse 在初始化 HTTPS 请求时,会自动调用 Windows API 去寻找这 2 个 DLL,查找顺序如下(从上到下,找到即停止):
- 当前进程的内存中(如果主程序启动时已经用
LoadLibrary提前加载过)。 - 程序执行文件 (
.exe) 所在的当前根目录(⭐ 最推荐的做法)。 - Windows 系统目录(
C:\Windows\System32或SysWOW64)。 - 系统环境变量
PATH中包含的任意目录。
防坑警告:为了防止被其他软件残缺版本的 DLL 劫持,请务必把这两个 DLL 和你的 EXE 放在同一个文件夹下。
🛠️ 3. 类参考手册 (Class Reference)
3.1 实例化与生命周期
var Client: TSynHttpClient;
Client := TSynHttpClient.Create; // 创建并自动初始化默认参数
Client.ResetSynHttp; // 重置状态(如果在循环中复用 Client 且需要改配置,可调用此方法)
Client.Free; // 销毁实例,并自动安全关闭底层的 TCP/Socket 通道
3.2 常用属性 (配置类)
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
Timeout |
Integer | 15000 (15秒) |
网络建连与数据读写超时时间(毫秒)。此超时时间是真实有效受控的。 |
KeepAlive |
Boolean | False |
长连接开关。设为 True 时,TCP/TLS 通道在请求后不关闭。仅在高频批处理场景下开启,单次交互请保持 False。 |
Protocol |
string | '1.1' |
HTTP 协议版本。强制使用 1.1 以支持长连接分块等现代特性。 |
MimeType |
string | 'application/json' |
决定 Content-Type 的值。请求 WebService 时可改为 text/xml; charset=utf-8。 |
UserAgent |
string | 现代 Chrome 标识 | 伪装浏览器,防止被国内政务 WAF 视为爬虫直接拦截。 |
ProxyHost |
string | '' |
代理服务器 IP(如需抓包或穿透内网,填入代理IP,如 127.0.0.1)。 |
ProxyPort |
string | '' |
代理服务器端口(如 7890)。 |
AutoUTF8 |
Boolean | True |
防乱码开关。发送和接收时自动在本地编码与 UTF-8 之间双向转换。 |
SaveLog |
Boolean | True |
遇到非 200~299 或网络断开时,是否自动写 .txt 日志到本地。 |
CustomHeaders |
TStringList | TStringList.Create |
动态添加额外请求头参数,如 Token 或签名。 |
3.3 方法 (Method)
function SynDoGet(const AURL: string): Boolean;
发起 GET 请求。function SynDoPost(const AURL, AJsonOrFormBody: string): Boolean;
发起 POST 请求,第二个参数传入 JSON 或 XML 字符串。procedure AddCustomHeaders(sKey, sValue: string);
向请求头中动态添加参数,如 Token 或签名。示例:Client.AddCustomHeaders('Authorization', 'Bearer 123');
3.4 结果属性 (只读,调用后获取)
⚠️ 【极其重要】: 认清三个判断标准的区别
SynDoPost的返回值 (Boolean):代表网络物理层是否通畅(底层 Socket 是否报错)。StatusCode:代表服务端收到了请求并返回的真实 HTTP 状态码(如 200, 400, 401, 500)。BusiSuccess:代表业务逻辑是否成功(仅当 StatusCode 在 200~299 之间且Sock.LastError 为 0 时为 True)。
| 属性名 | 类型 | 描述 |
|---|---|---|
BusiSuccess |
Boolean | 业务成功标志。当 200 <= StatusCode < 300 且无报错时为 True。 |
ErrorMessage |
string | 前端展示神器:如果是断网,返回底层 Socket 错误;如果是服务器报 500,返回服务端吐出的 JSON 错误信息。 |
ResponseStr |
string | 服务器返回的响应体内容(已根据 AutoUTF8 自动解码为不乱码的本地字符串)。 |
StatusCode |
Integer | HTTP 状态码。 |
LastError |
Integer | 底层 Windows Socket 错误码(如 10061 拒绝连接,10060 超时)。 |
💻 4. 典型业务场景实战代码
场景 1:最基础的 GET/POST 请求 (保持 KeepAlive=False)
适用场景:用户点击界面按钮查询单笔数据。
procedure TForm1.BtnPostClick(Sender: TObject);
var
Client: TSynHttpClient;
begin
Client := TSynHttpClient.Create; // 默认 KeepAlive=False,用完即焚最安全
try
Client.Timeout := 5000;
Client.AddCustomHeaders('Signature', 'ABCDEF123456');
Client.SynDoPost('https://opendata.baidu.com/api.php?query=114.114.114.114&co=&resource_id=6006&oe=utf8', '{"a":"a"}');
if Client.BusiSuccess then
ShowMessage('交易成功!平台返回:' + Client.ResponseStr)
else
ShowMessage('交易失败:' + Client.ErrorMessage); // 自动显示网络错误或服务器JSON报错
finally
Client.Free;
end;
end;
procedure TForm1.BtnGetClick(Sender: TObject);
begin
with TSynHttpClient.Create do
try
ResetSynHttp;
Timeout := 3000; //缺省值=15000 此处可动态修改,且稳定生效
SynDoGet(Trim(https://opendata.baidu.com/api.php?query=114.114.114.114&co=&resource_id=6006&oe=utf8?a=1614840052));
if BusiSuccess then //业务成功
begin
//解析业务数据
ShowMessage('业务成功:' + #13#10 + ResponseStr);
end else
begin
ShowMessage('业务失败:' + #13#10 + ErrorMessage);
end;
finally
Free;
end;
end;
procedure TForm1.ButtonformClick(Sender: TObject);
var
sSendData, sUrl: string;
begin
with TSynHttpClient.Create do
try
ResetSynHttp;
Timeout := 3000; //缺省值=15000 此处可动态修改,且稳定生效
MimeType := 'application/x-www-form-urlencoded';
sUrl := 'https://api.totalshiftleft.ai/openapi/pay/query';
sSendData := 'mch_id=000001' + '&' +
'nonce_str=02hsddhdh' + '&' +
'a=1614840052' + '&' +
'billno=000000001';
SynDoPost(sUrl, sSendData);
if BusiSuccess then //接口调用成功
begin
//解析业务数据
ShowMessage('业务成功:' + #13#10 + ResponseStr);
end else
begin
ShowMessage('业务失败:' + #13#10 + ErrorMessage);
end;
finally
Free;
end;
end;
场景 2:调用传统的 WebService (SOAP XML)
无需使用 Delphi 笨重的 WSDL 导入器,直接利用本类发送文本即可,HTTP/HTTPS 都支持!
procedure TForm1.BtnSoapClick(Sender: TObject);
var
Client: TSynHttpClient;
SoapBody: string;
begin
Client := TSynHttpClient.Create;
try
Client.MimeType := 'text/xml; charset=utf-8';
<!-- Client.AddCustomHeaders('SOAPAction', '"http://tempuri.org/GetPersonInfo"'); -->
SoapBody := '<?xml version="1.0" encoding="utf-8"?><soap:Envelope ...><a>1614840052</a></soap:Envelope>';
if Client.SynDoPost('https://demo.totalshiftleft.ai/soap?wsdl', SoapBody) then
begin
if Client.BusiSuccess then
ShowMessage('WebService 调用成功: ' + Client.ResponseStr);
end;
finally
Client.Free;
end;
end;
🔥 场景 3:极速批处理 (开启 KeepAlive = True)
适用场景:每天凌晨定时向Restful密集上传成千上万条记录。
为什么要开? 如果上传 10000 次,开启 KeepAlive 可以省去 10000 次 TCP 握手和 TLS 证书协商时间,耗时从几十分钟骤降至几分钟!
procedure TForm1.BatchUpload;
var
Client: TSynHttpClient;
i: Integer;
begin
Client := TSynHttpClient.Create;
try
// 1. 开启极速长连接模式
Client.KeepAlive := True;
Client.Timeout := 3000; //缺省值=15000 此处可动态修改,且稳定生效
Client.MimeType := 'application/json';
Client.AddCustomHeaders('Authorization', 'Bearer XXXX');
Client.AddCustomHeaders('a', '1614840052');
// 2. 在一个循环中,复用同一个 Client 实例高频发送
for i := 1 to 10000 do
begin
Client.SynDoPost('https://api.example.com/upload', '{"id":' + IntToStr(i) + '}');
if not Client.BusiSuccess then
Log('第' + IntToStr(i) + '条失败: ' + Client.ErrorMessage);
end;
finally
// 3. 循环结束,Free 时自动发送 FIN 包优雅断开 TCP 通道
Client.Free;
end;
end;
🚦 5. 高阶排坑指南:KeepAlive (长连接) 的正确使用姿势
针对 KeepAlive 属性,请团队研发人员务必牢记以下设计规范:
- 绝对不要在低频 UI 交互中开启它:
如果你的请求是用户手动点击触发的,或者两次请求的间隔超过 10 秒钟,必须使用默认的KeepAlive = False。因为现代服务器网关 (如 Nginx) 通常会在 60 秒闲置后单方面悄悄掐断连接。如果在低频场景滥用长连接,极易引发底层报10054 Connection reset by peer的玄学错误。 - KeepAlive 生死权在服务端:
HTTP 的 KeepAlive 超时时间由服务端网关控制(而不是客户端)。客户端的任务就是在高频for循环中复用通道榨干性能,用完后立即释放。 - 每次请求自动刷新机制:
本类在底层已经做了“脏头清洗”机制。在长连接for循环期间,无需担心上一次请求的服务端响应头会污染下一次的请求头,直接调用SynDoPost即可。 - 自动重连策略:
Keep-alive模式下,无需担心长连接断开,Synapse在每次请求前会向操作系统咨询当前通道是否可读,如果不可读会重新建立通道并进行TCP和TLS握手。
📁 6. 自动错误日志追踪 (Troubleshooting)
为了解决实施人员在客户现场难以复现网络故障的问题,组件内置了 SaveHttpErrLogs 功能。
当满足以下条件时,组件会自动触发写日志:
SaveLog = True(默认开启)- 业务未成功(
BusiSuccess = False),即网络断开、超时,或者服务端返回了 400、500 等错误。
存放路径:
日志会自动存放在程序同目录下的 SynHttpLogs 文件夹中。按天分文件,如 20260825.txt。
日志内容解析(示例):
------------------<2026-8-25 17:1:22:845>---------------------
<URL>https://xxxxx/api.php?query=114.114.114.114&co=&resource_id=6006&oe=utf8</URL>
<RequestHeader>Host: xxxx</RequestHeader>
<RequestBody>{"apikey": "xx","id": 10001}</RequestBody>
<HTTPMethod>POST</HTTPMethod>
<StatusCode>400</StatusCode>
<StatusText>Bad Request</StatusText>
<ResponseBody>HTTP Error 400. The request has an invalid header name.</ResponseBody>
<Remote>opendata.baidu.com</Remote>
<LastError>0</LastError>
------------------<2026-8-25 17:1:22:845>---------------------
(通过该日志,研发人员可以直接还原发出的确切头部和 Body,快速排查是传参错误、网络错误还是服务端挂机。)
7. 集成模块到现有项目
- 拷贝”ssllib“文件夹到项目根目录下面,然后在工程文件夹中Search Path中添加”ssllib“文件夹的路径。
- 在需要使用的单元中uses utXrxHttps 即可。
❤️ ~~~~~~~~~~~~~