版本 2026.9.0:sgcWebSockets、sgcSign、sgcIndy 与 sgcOpenAPI

· 版本发布
面向 Delphi、C++ Builder 和 .NET 的 eSeGeCe 组件库 2026.9.0 版本

2026.9.0 面向全部产品同时发布:sgcWebSockets、sgcWebSockets .NET、sgcSign、sgcIndy 和 sgcOpenAPI。这是今年最大的一次发布。两个全新的包加入了 sgcWebSockets,服务端引擎围绕一个长期导致请求丢失的问题做了重构,签名库也终于学会了用签名自身之外的东西去验证一个签名。

本文逐一介绍每个产品中真正重要的内容,需要时附上 Delphi 代码。全部细节都在更新日志里,而会改变现有行为的部分集中放在文末,方便你在升级之前先读一遍。

四分钟内看完整个版本。 也可在 YouTube 观看.

产品新增修复破坏性变更
sgcWebSockets255622
sgcSign257021
sgcOpenAPI9255
sgcWebSockets .NET52410
sgcIndy050

sgcWebSockets 2026.9.0

sgcWebRTC,原生 WebRTC 媒体引擎

全新的 sgcWebRTC 包把 TsgcRTCPeerConnection 变成了一个完整的 WebRTC 端点。音视频通话、屏幕共享和 SCTP 数据通道,全部用 Pascal 实现,不需要浏览器,不需要 WebView 控件,也不需要外部媒体库。它可以运行在 Windows、Linux、macOS、iOS 和 Android 上,而另一端的对等方可以是浏览器,因为它生成和解析的 SDP 是货真价实的。

offer 与 answer 的交换遵循 W3C 的形式,因此 CreateOfferCreateAnswerSetLocalDescriptionSetRemoteDescriptionAddIceCandidate 的作用与它们的名字完全一致。你可以通过手头已有的任何信令通道传递 SDP,它甚至可以是用同一个库搭建的 WebSocket 服务器。

uses
  sgcP2P;

var
  oRTC: TsgcRTCPeerConnection;
begin
  oRTC := TsgcRTCPeerConnection.Create(nil);
  oRTC.RTCOptions.ICEServers.AddURL('stun:stun.l.google.com:19302');
  oRTC.OnLocalDescription      := OnLocalDescriptionHandler;
  oRTC.OnICECandidate          := OnICECandidateHandler;
  oRTC.OnConnectionStateChange := OnConnectionStateChangeHandler;
  oRTC.OnDataChannel           := OnDataChannelHandler;

  oRTC.CreateDataChannel('chat');   // forces RTCOptions.DTLS on
  oRTC.CreateOffer;                 // gathers ICE candidates, builds the SDP offer
end;

procedure TForm1.OnLocalDescriptionHandler(Sender: TObject;
  const aType, aSDP: string);
begin
  // send aType + aSDP to the remote peer over your own signalling channel
end;

媒体的用法也一样。AddTrack 挂载一路 Opus 或 G.711 音频轨,或者一路 VP8、H.264 或 Motion JPEG 视频轨,SendPCMSendVideoFrame 把采集到的媒体推进去,OnAudioOnVideoFrame 则把解码后的远端轨交给你。

那些浏览器时代的旧回退方案则走向了相反的方向。AppRTC 协议和 RTCMultiConnection API 的支持已被移除,2020 年就已终止生命周期的 Flash 回退同样被移除。请参阅文末的升级说明。

sgcCrypto,不依赖 OpenSSL 的加密库

第二个新包是 sgcCrypto,它用纯 Pascal 实现了大多数应用真正会用到的密码学原语。AES 与 ChaCha20/XChaCha20-Poly1305 认证加密,SHA-2 与 SHA-3,用于口令和密钥派生的 Argon2、scrypt 与 HKDF,Ed25519、Ed448、X25519、X448 与 secp256k1,RSA 密钥生成,X.509 证书与 CSR 生成,以及后量子的 ML-KEM、ML-DSA 和 SLH-DSA 算法。

每一个原语都只是一个普通函数。没有需要创建、配置和释放的上下文对象,也不需要在可执行文件旁边附带 DLL。

uses
  sgcCrypto_Random, sgcCrypto_AES, sgcCrypto_Keccak, sgcCrypto_Ed25519,
  sgcCrypto_MLKEM;

var
  vKey, vIV, vPlain, vAAD, vTag, vCipher: TBytes;
  vDigest, vSeed, vSignature, vMessage: TBytes;
  vPublicKey, vPrivateKey, vSharedSecret, vCiphertext: TBytes;
begin
  // AES-256-GCM: authenticated encryption in one call
  vKey    := sgcRandomBytes(32);
  vIV     := sgcRandomBytes(12);
  vPlain  := TEncoding.UTF8.GetBytes('confidential payload');
  vCipher := sgcAES_GCM_Encrypt(vKey, vIV, vPlain, vAAD, vTag);

  // SHA-3-256, one call, no context object to manage
  vDigest := sgcSHA3_256(vPlain);

  // Ed25519: sign, then verify
  vSeed      := sgcRandomBytes(32);
  vMessage   := TEncoding.UTF8.GetBytes('sign me');
  vSignature := sgcEd25519_Sign(vSeed, vMessage);

  // ML-KEM-768: post-quantum key encapsulation (FIPS 203)
  sgcMLKEM_GenerateKeyPair(mlkem768, vPublicKey, vPrivateKey);
  sgcMLKEM_Encapsulate(mlkem768, vPublicKey, vCiphertext, vSharedSecret);
end;

五个完整的 sgcHTML 应用

sgcHTML 新增了四个组件和五个演示。这些组件包括 CameraScanner,一个实时摄像头面板,用浏览器内置的扫描器读取条形码和二维码,并始终提供手动输入的回退方式,此外还有 NumPad、CommandPalette 和 EmptyState。

这五个演示不是代码片段。每一个都是带登录、数据库和可打印报表的完整应用,它们位于 Demos\60.HTML\01.RunTime 之下:仓库管理、销售终端、报表门户、多租户 SaaS 控制台,以及现场服务派工。

还有一个新的帮助主题 Runtime vs Design-Time,配套的演示展示了如何把组件拖放到 VCL 窗体上来构建同一个页面,而不是用代码去组合它。

反向代理背后的真实客户端地址

位于 nginx、Apache 或云负载均衡器之后的服务器,在每一个连接上看到的都是代理的地址,这意味着黑名单、白名单、GeoIP 以及你自己的规则,看到的都是错误的客户端。TsgcWebSocketFirewall 上新增的 ForwardedHeaders 设置可以还原出代理转发过来的地址。

这个设计刻意保持怀疑态度。只有当连接本身来自 TrustedProxies 中列出的地址时,X-Forwarded-ForX-Real-IP 中的地址才会被采信,因此客户端无法凭空伪造一个;而且这条链是从右侧开始读取的,跳过 TrustedHops 中给出的代理数量,因为最左边的那一项正是客户端自己提供的。解析在每一个请求上都会执行,因为一个代理会用同一条连接承载来自不同调用方的请求。

uses
  sgcWebSocket;

begin
  oFirewall.ForwardedHeaders.Enabled := True;
  oFirewall.ForwardedHeaders.TrustedProxies.Add('10.0.0.0/8');
  oFirewall.ForwardedHeaders.TrustedProxies.Add('::1');
  oFirewall.ForwardedHeaders.TrustedHops := 1;

  oServer.Firewall := oFirewall;
end;

procedure TForm1.OnServerConnectHandler(Connection: TsgcWSConnection);
begin
  // Connection.IP is now the end client
  // Connection.PeerIP is still the proxy that opened the socket
end;

一旦解析完成,每一条黑名单项、每一条自定义规则、每一个事件以及你自己的处理代码看到的都是这个地址。这些设置在 http.sys 服务器上的行为与在 Indy 服务器上完全一致,同样的功能也以 server.firewall.forwarded_headers 的形式进入了 sgcSign Server。

可以度量的背压

向一个慢速客户端写入的速度超过它读取的速度,发送队列就会不断增长,直到某个环节撑不住。以前无法从应用程序内部看到这件事正在发生,于是中继只能在链路上做一来一回的停等交互,仅仅是为了保证安全。

两个新增内容取代了这种做法。TsgcWSConnection 上的 PendingCount 报告该连接在三个优先级上还有多少条消息在排队,读取它不会产生任何内存分配,因此可以轮询。OnQueueDrained 会在一个连接的队列从有消息变为空时触发,运行在连接线程上,就在队列排空之后、下一次读取之前,因此可以毫无延迟地发放一个额度。

procedure TForm1.OnQueueDrainedHandler(Connection: TsgcWSConnection);
begin
  // raised on the connection thread: do not touch the user interface here
  SendNextBatch(Connection);
end;

procedure TForm1.SendIfRoom(Connection: TsgcWSConnection; const aText: string);
begin
  if Connection.PendingCount < 100 then
    Connection.WriteData(aText);
end;

两者都跨越了 DLL 边界,因此 .NET 封装以及任何其他使用 sgcWebSockets.dll 的宿主同样可以用上它们。新的导出项是追加在末尾的,现有的导出顺序保持不变。

EPOLL 与 IOCP 引擎

这两个高性能引擎存在一类缺陷,根源只有一个:它们假定一个请求会在单次套接字读取中全部到达。这在回环测试中成立,但在任何 MTU 边界上、通过 VPN 时,或者只要一个较大的消息体被拆分到多个数据包中,就不成立了。

没有完整到达的请求会被丢弃,连接被关闭且不返回任何响应;而被拆分到两个 TCP 分段中的 TLS 记录会直接导致连接关闭。两者都已修复,新增的 PartialRequestTimeout 选项限定了服务器等待剩余数据的时长,并且运行在独立的线程池上,这样一个慢速客户端就无法拖住其他客户端。在 EPOLL 引擎上,客户端关闭连接之后服务端连接从来不会被释放,因此 OnDisconnect 从不触发,每一次广播都会继续往一个已经死掉的连接里写数据。

这也是 WorkOpThreads 含义发生变化的原因。它不再把一个连接固定绑到某一个线程上,因为一个正在等待请求剩余部分的连接会拖住同一线程上的所有其他连接。它现在设置的是保持就绪的最小工作线程数量,线程池会在此基础上按需增长。

QUIC 与 HTTP/3

QUIC 与 HTTP/3 的客户端和服务器现在支持 IPv6。带冒号的地址会被当作 IPv6 处理,URL 中可以用方括号书写它,主机名会在两个地址族上解析,而此前只会尝试 IPv4。未设置主机的 HTTP/3 监听器通过单个套接字同时服务两个地址族,HTTP3Options 上新增的 Host 属性可以在你需要时把它固定到某一个网络接口上。

更大的变化是,HTTP/3 请求现在走的是与 HTTP/1.1 请求相同的路径。OpenAPI、MCP 和 REST API 服务器、请求转发、CORS、多租户和指标此前在 HTTP/3 上完全不会响应,因为那个传输层有一条自己的路径。现在它们全部可用了。

HTTP/3 上的证书验证是需要读两遍的一条。TsgcHTTP3Client 上的 TLSOptions.VerifyCertificate 默认为 True,检查也确实执行了,但结果随后被丢弃,因此每一个 HTTP/3 客户端都会接受任何机构签发给任何主机的任何证书。现在这个检查结果会被真正采用,这意味着连接到自签名端点的 HTTP/3 客户端将无法再建立连接,直到信任库或证书被修正为止。

交易所 API 客户端

这些现成的交易所客户端有一个重连问题,而它表现出来的却像是另一个古怪得多的问题。在 Bitstamp、Coinbase、Deribit、Huobi、Kraken 现货与期货、Kucoin、MEXC 和 ThreeCommas 上,私有订阅,也就是订单、余额和成交推送,在重连之后从来不会被重放。组件报告重连成功,而那些推送却悄无声息地消失了。在 BitMEX、Bitfinex、Crypto.com、Deribit 和 Kraken 期货上,重放发生在认证之前,这同样行不通。携带短时效凭证的帧现在会在重放时用新的凭证重新构建,而不是原样重发。

金额在被签名之前就已经被四舍五入了。固定的八位小数掩码会把 0.000000004 这样的数量变成零,而且数值可能以科学计数法输出,或者根据系统区域设置使用逗号作为分隔符。在 Binance、Bybit、Cex、Cryptohopper、Kucoin、MEXC 和 ThreeCommas 上,数值现在都以无损方式、普通十进制、使用点号写出。

同一领域的新增内容:在 v1 组件之外新增了 Kraken WebSocket v2 组件;一个 Huobi 客户端会在内部再开一条连接,使单个实例同时服务公有和私有数据;Binance 上新增 UseServerTimeOffset,用于时钟已经漂移的主机;还有两个限流选项 PaceBatchAsyncResubscribe,它们可以避免大量订阅触发交易所的速率限制,并把重连时的重放移到后台工作线程上。

sgcWebSockets .NET 2026.9.0

.NET 库与 Delphi 版本保持同步。OnQueueDrainedPendingCount 以相同的含义加入,同时加入的还有 Throttle.AsyncResubscribe、Cryptohopper 客户端上的 AllowUnsignedWebhooks,以及新的 OnBinanceUserStreamSubscribed 事件,这样就不必再靠轮询属性来判断用户数据流是否就绪。

上面列出的每一项交易所重连与签名修复在这里同样适用,被移除的内容也一样。AppRTC 协议、RTCMultiConnection API 和 Flash 回退类已经不在了,这会让 TwsTransport 重新编号。曾经持久化或传输过该传输方式数值的代码需要重新检查。

有两处泄漏值得一提:一个 WebSocket API 组件在其客户端仍处于连接状态时被释放,却从未把自己从消息处理器遍历的那个列表中移除;另外每销毁一个组件就会泄漏两个心跳定时器线程,这正是干净关闭时偶尔会出错的原因。

sgcSign 2026.9.0

这个版本的大部分内容来自客户的需求,落在三个方面:清楚知道自己即将用哪一张证书签名,构建出十年后验证器仍然会接受的签名,以及用签名自身之外的东西去验证一个签名。

可供你选择的证书列表

枚举证书以前只返回显示名称,这足以填满一个下拉框,却不足以让你做出决定。来自同一机构、签发给同一个人的两张卡,在那份列表里看起来一模一样。现在枚举结果会带上 SHA-1 指纹、税务识别号、序列号、签发者和有效期,Windows 证书存储、PKCS#11 令牌和 PFX 文件的处理方式完全一致。

uses
  sgcSign_KeyProvider_WinCertStore, sgcSign_X509, sgcSign_Types;

var
  oProvider: TsgcWindowsCertStoreProvider;
  oList: TsgcX509CertificateList;
  i: Integer;
begin
  oProvider := TsgcWindowsCertStoreProvider.Create(nil);
  Try
    // only certificates that are still valid and hold a private key
    oList := oProvider.EnumerateCertificateList([cfNotExpired, cfPrivateKey]);
    Try
      for i := 0 to oList.Count - 1 do
        Memo1.Lines.Add(Format('%s | %s | %s .. %s | %s',
          [oList[i].Subject, oList[i].SerialNumber,
           DateToStr(oList[i].NotBefore), DateToStr(oList[i].NotAfter),
           oList[i].Thumbprint]));

      // and sign with exactly the one that was chosen
      oProvider.SelectCertificateByThumbprint(oList[0].Thumbprint);
    Finally
      oList.Free;
    End;
  Finally
    oProvider.Free;
  End;
end;

多插槽的 PKCS#11 卡,在把不同证书放在不同 PIN 之后的波兰合格签名卡中很常见,现在完全不需要 PIN 就可以清点。EnumerateCertificateListAllSlots 会读取每一个插槽,每一条记录都标明它来自哪里,TokenSlotCount 则报告实际装有令牌的插槽数量。

证书本身现在也会报告它所携带的全部内容,而不再只是解析器过去认识的那七个属性,其中通信地址会被解码成可读的行,任何属性都可以通过它的 OID 取到。

始终可验证的签名

PAdES 新增两个级别。spPAdESBasicT 签名时嵌入一个时间戳而不包含吊销数据,spPAdESDocumentArchive 则在长期级别之上再加一个归档时间戳,覆盖整个文档以及其中的吊销数据,这样即使第一个时间戳自身的有效期已经过去,文件依然可以被验证。

以前找到签发证书是你自己的事,因为大多数合格签名卡只携带你自己的证书。新的 GetIssuerCertificateGetCertificateChain 调用会找出签发你正在使用的那张证书的证书,以及它之上的整条路径,并且以密码学方式匹配而不是按名称匹配,因此更换过签名密钥的机构不会和它的前身混淆。IssuerLookup 决定去哪里查找:默认是 Windows 证书存储,也可以是你随产品分发的 PEM 或 DER 文件,或者证书内部记录的地址,后者默认关闭。

时间戳请求现在可以被签名,某些合格机构要求这样做,波兰的机构尤其如此。把 RequestFormat 设为 trfCMS,指定一个密钥提供者,OnBeforeSendRequestOnAfterReceiveResponse 就会把发送和接收到的确切字节交给你。

带信任锚的验证

这是需要仔细阅读的一项变更。在此之前,验证器会从它正在检查的文档中取出签名证书,然后确认该密钥确实签过这份文档,而这只能证明写这份文档的人同时也写了里面的那个签名。

新的 TrustedCertificatesTrustedCertificateStore 属性用来声明你信任哪些根证书,RequireTrustedChainCheckKeyUsageRequireCompleteRevocationCheck 则决定结论的严格程度。信任锚通过 SHA-256 指纹匹配,或者通过用它自己的密钥验证来匹配,绝不按名称匹配。没有配置信任锚的验证器返回的结论与以前相同,但对于从未链接到信任锚的签名,ETSI TS 119 102-2 报告不再给出 total-passed,因此在没有信任锚的情况下生成并保存的报告需要重新生成。

Authenticode 签名现在可以嵌入额外的证书,也就是 signtool /ac 所做的事情,这样一个内核模式驱动的签名就可以通过交叉证书链接到 Microsoft Code Verification Root。

统一的 HTTP 传输层,支持代理

库发出的每一个请求现在都经过同一个传输层,共享一个 HTTPOptions 属性:时间戳客户端、OCSP 和吊销列表客户端、欧盟信任列表下载以及云端密钥提供者。它承载代理设置,可以是系统代理、不使用代理、显式指定的地址,或者由机器按目标地址逐一解析出的代理;也承载需要认证的代理的凭据、客户端证书、可接受的最低 TLS 版本以及用户代理字符串。每一项设置的默认值都与这些请求以前的行为一致,新的 OnHTTPRequest 事件则可以在这些设置无法描述某个网关时,整体替换掉传输层。

签名服务器

API 密钥以及创建它们的用户现在按项目隔离,因此项目管理员可以管理自己项目中的密钥,而看不到其他人的。密钥可以启用和禁用,而不再只能单向吊销;它们的速率限制和每日配额在创建之后可以修改;审计日志终于可以按客户端地址筛选,控制台和 CSV 导出中都可以。

一个 Authenticode 签名现在可以携带超过两个嵌套签名,并且每一个都使用不同的证书,交付一个同时由即将过期的证书及其替代证书签名的文件正需要这一点。Windows 目录文件现在可以签名,新的 /api/v1/sign/raw 端点会对你已经计算好的摘要进行签名并只返回签名值,这正是 signtool 通过它的 /dlib 回调所要求的。由于它会对交给它的任何摘要签名,该端点默认关闭,并且需要逐个提供者单独开启。

有几项服务器默认值出于充分的理由发生了变化。server.listen 设置现在真的会绑定你给出的地址,管理操作要求提交携带一次性令牌的表单,通过 HTTPS 投递的 webhook 会校验目标地址的证书。这三项都写在升级说明里。

sgcIndy 2026.9.0

这是一个小版本,但其中每一项都是值得拥有的修复。

通过 ALPN 协商出的协议名称,被以四个字节写入了 OpenSSL 用于保存其长度的那一个字节中,因此每一次协商了协议的握手都会覆盖相邻的内存。运行在 IOCP 或 EPOLL 引擎上的 TLS 服务器,在对端要求重新协商时会耗尽一个线程的全部 CPU,它一遍又一遍地重试写入,而不是把 OpenSSL 已经准备好的数据发送出去。在 Linux 上,当对端在写入过程中重置连接时,TLS 应用会被操作系统关闭,因为 OpenSSL 的写入方式无法要求系统抑制管道破裂信号。FPC 和 Lazarus 下的同一问题也已修复,包括 macOS 上由服务器接受的连接,它们此前仍然存在这个隐患。

对于 Community 安装包,预编译的 Delphi 13 二进制文件是由 Delphi 12 的工程生成的,因此它们带的是 290 命名,而不是 Delphi 13 安装程序所期望的 370 命名,而且 Windows ARM64EC 根本没有被构建。Delphi 13 现在会为每一个受支持的平台构建自己的工程。

sgcOpenAPI 2026.9.0

解析器不再悄无声息地失败。现在每一份文档都会返回一个 Warnings 列表,其中记录缺失的 openapiinfo 成员、JSON 类型错误的成员、无法生成的操作、未解析的 path item 引用,以及任何已读取但尚未支持的 JSON Schema 关键字。

uses
  sgcOpenAPI_Classes, sgcOpenAPI_Parser_Client_Pascal;

var
  oParser: TsgcOpenAPI_Parser_Client_Pascal;
  i: Integer;
begin
  oParser := TsgcOpenAPI_Parser_Client_Pascal.Create;
  Try
    oParser.OpenAPIClassName := 'TPetStoreClient';
    oParser.OutputFileName := 'PetStoreClient.pas';

    oParser.ReadFromFile('petstore.json');

    for i := 0 to oParser.Warnings.Count - 1 do
      Memo1.Lines.Add('warning: ' + oParser.Warnings[i]);

    oParser.SaveToFile('PetStoreClient.pas');
  Finally
    oParser.Free;
  End;
end;

它现在也知道自己正在读取的是规范的哪个版本,因此 3.0 文档和 3.1 文档不再被当作同一回事,exclusiveMinimumexclusiveMaximum 就是最明显的例子。OpenAPI 3.1 的 webhooks、jsonSchemaDialectcomponents.pathItems、许可证标识符、mutualTLS 安全方案、声明为数组的类型(例如 ["string","null"])以及声明为布尔值的 schema 现在都受支持,OpenAPI 3.2 的 query 操作和 additionalOperations 映射同样受支持。

代码生成在以往会放弃的地方变得更好了。内联的对象 schema 现在会生成自己的类,而不再降级为字符串,items 会作为一个完整的 schema 读取,生成的客户端支持 cookie 参数以及完整的参数序列化规则:matrix、label、simple、form、spaceDelimited、pipeDelimited 和 deepObject,并支持 explode 与 allowReserved。

命令行工具终于可以用在构建脚本里了。它会设置退出码,成功为 0,不同的失败情况为 1 到 7,错误信息始终输出到标准错误。新的 -r 开关通过公共转换器转换 YAML 或 Swagger 2.0 文档,默认关闭。

同样的 OpenAPI 改进也随 sgcWebSockets 一起发布,其中服务端验证器现在还会检查 header 和 cookie 参数,对应 ValidateHeaderParamsValidateCookieParamsEnforceRequired

升级之前

这一次每个产品都有破坏性变更。下面这些是最可能影响到你的。

完整的列表以及每一项的原因,都在各个产品的更新日志中。

如何获取

版本 2026.9.0 现已发布,包含完整源代码和一年的更新,支持 Delphi 7 到 Delphi 13 Florence、对应的 C++ Builder 版本,以及 .NET。

sgcWebSockets · sgcWebRTC · sgcCrypto · sgcHTML · sgcSign · sgcIndy · sgcOpenAPI

下载试用版 · 更新日志

有问题或建议?联系我们,回复你的将是编写这些代码的人。