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

如何无需解析错误字符串从THTTPClient/WinHTTP获取错误码?

从ENetHTTPClientException中获取WinHTTP错误码的方法

问题描述

我尝试从THTTPClient抛出的ENetHTTPClientException异常中提取WinHTTP错误码,但直接调用GetLastError()始终返回0,无法得到预期的超时错误码12002。

异常信息

Class name:    ENetHTTPClientException
Message:       Error sending data: (12002) Das Zeitlimit für den Vorgang wurde erreicht
Unit name:     System.Net.HttpClient

示例代码

var
  http: THTTPClient;
  request, response: TStringStream;
  httpresponse: IHTTPResponse;
  lasterror: Cardinal;
begin
  http := THTTPClient.Create;
  request := TStringStream.Create('', TEncoding.UTF8);
  response := TStringStream.Create('', TEncoding.UTF8);
  try
    try
      httpresponse := http.Post('url that runs into timeout', request, response);
    except
      on E: ENetHTTPClientException do begin
        lasterror := GetLastError; // 预期超时错误码12002,但实际为0
        ShowMessage(IntToStr(lasterror));
      end;
    end;
  finally
    response.Free;
    request.Free;
    http.Free;
  end;
end;

内部错误处理逻辑

TWinHTTPClient的错误处理实现如下,可见它确实获取了WinHTTP错误码并用于构造异常消息,但并未将错误码暴露出来:

function TWinHTTPClient.HandleExecuteError(AErrorMsg: PResStringRec; const ARequest: THTTPRequest): TWinHTTPClient.TExecutionResult;
var
  LastError: Cardinal;
begin
  LastError := GetLastError;
  case LastError of
    ERROR_WINHTTP_SECURE_FAILURE:
      if (SecureFailureReasons <> [THTTPSecureFailureReason.SecurityChannelError]) and
         not TWinHTTPRequest(ARequest).FServerCertificateAccepted then
        Exit(TExecutionResult.ServerCertificateInvalid)
      else
        raise ENetHTTPClientException.CreateResFmt(AErrorMsg, [LastError, SysErrorMessage(LastError, TWinHttpLib.Handle)]);
    ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED:
      Exit(TExecutionResult.ClientCertificateNeeded);
    ERROR_WINHTTP_RESEND_REQUEST:
      Exit(TExecutionResult.Retry);
    else
      if (LastError = ERROR_WINHTTP_OPERATION_CANCELLED) and
         (SecureFailureReasons = [THTTPSecureFailureReason.CertNotAccepted]) then
        raise ENetHTTPCertificateException.CreateRes(@SNetHttpServerCertificateNotAccepted)
      else
      if (LastError = ERROR_WINHTTP_OPERATION_CANCELLED) or TWinHTTPRequest(ARequest).FCancelled then
        Exit(TExecutionResult.Success)
      else
        raise ENetHTTPClientException.CreateResFmt(AErrorMsg, [LastError, SysErrorMessage(LastError, TWinHttpLib.Handle)]);
  end;
end;

解决方案

1. 自定义异常子类扩展错误码属性

原生ENetHTTPClientException未公开错误码,可自定义继承它的异常类,添加错误码属性,并通过类助手替换TWinHTTPClient的错误抛出逻辑:

// 自定义异常类
type
  EWinHTTPException = class(ENetHTTPClientException)
  private
    FErrorCode: Cardinal;
  public
    constructor Create(const Msg: string; ErrorCode: Cardinal);
    property ErrorCode: Cardinal read FErrorCode;
  end;

constructor EWinHTTPException.Create(const Msg: string; ErrorCode: Cardinal);
begin
  inherited Create(Msg);
  FErrorCode := ErrorCode;
end;

// 类助手扩展TWinHTTPClient的错误处理
type
  TWinHTTPClientHelper = class helper for TWinHTTPClient
  public
    function HandleExecuteErrorEx(AErrorMsg: PResStringRec; const ARequest: THTTPRequest): TExecutionResult;
  end;

function TWinHTTPClientHelper.HandleExecuteErrorEx(AErrorMsg: PResStringRec; const ARequest: THTTPRequest): TExecutionResult;
var
  LastError: Cardinal;
  ErrorMsg: string;
begin
  LastError := GetLastError;
  case LastError of
    ERROR_WINHTTP_SECURE_FAILURE:
      if (SecureFailureReasons <> [THTTPSecureFailureReason.SecurityChannelError]) and
         not TWinHTTPRequest(ARequest).FServerCertificateAccepted then
        Exit(TExecutionResult.ServerCertificateInvalid)
      else
      begin
        ErrorMsg := Format(LoadResString(AErrorMsg), [LastError, SysErrorMessage(LastError, TWinHttpLib.Handle)]);
        raise EWinHTTPException.Create(ErrorMsg, LastError);
      end;
    ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED:
      Exit(TExecutionResult.ClientCertificateNeeded);
    ERROR_WINHTTP_RESEND_REQUEST:
      Exit(TExecutionResult.Retry);
    else
      if (LastError = ERROR_WINHTTP_OPERATION_CANCELLED) and
         (SecureFailureReasons = [THTTPSecureFailureReason.CertNotAccepted]) then
        raise ENetHTTPCertificateException.CreateRes(@SNetHttpServerCertificateNotAccepted)
      else
      if (LastError = ERROR_WINHTTP_OPERATION_CANCELLED) or TWinHTTPRequest(ARequest).FCancelled then
        Exit(TExecutionResult.Success)
      else
      begin
        ErrorMsg := Format(LoadResString(AErrorMsg), [LastError, SysErrorMessage(LastError, TWinHttpLib.Handle)]);
        raise EWinHTTPException.Create(ErrorMsg, LastError);
      end;
  end;
end;

捕获异常时直接读取错误码:

except
  on E: EWinHTTPException do begin
    lasterror := E.ErrorCode; // 直接获取12002
    ShowMessage(IntToStr(lasterror));
  end;
end;

2. 利用Exception.Data传递错误码

如果不想自定义异常类,可在抛出异常时将错误码存入Exception.Data字典,同样通过类助手修改错误处理逻辑:

type
  TWinHTTPClientHelper = class helper for TWinHTTPClient
  public
    function HandleExecuteErrorEx(AErrorMsg: PResStringRec; const ARequest: THTTPRequest): TExecutionResult;
  end;

function TWinHTTPClientHelper.HandleExecuteErrorEx(AErrorMsg: PResStringRec; const ARequest: THTTPRequest): TExecutionResult;
var
  LastError: Cardinal;
  Ex: ENetHTTPClientException;
begin
  LastError := GetLastError;
  case LastError of
    ERROR_WINHTTP_SECURE_FAILURE:
      if (SecureFailureReasons <> [THTTPSecureFailureReason.SecurityChannelError]) and
         not TWinHTTPRequest(ARequest).FServerCertificateAccepted then
        Exit(TExecutionResult.ServerCertificateInvalid)
      else
      begin
        Ex := ENetHTTPClientException.CreateResFmt(AErrorMsg, [LastError, SysErrorMessage(LastError, TWinHttpLib.Handle)]);
        Ex.Data['WinHTTPCode'] := LastError;
        raise Ex;
      end;
    ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED:
      Exit(TExecutionResult.ClientCertificateNeeded);
    ERROR_WINHTTP_RESEND_REQUEST:
      Exit(TExecutionResult.Retry);
    else
      if (LastError = ERROR_WINHTTP_OPERATION_CANCELLED) and
         (SecureFailureReasons = [THTTPSecureFailureReason.CertNotAccepted]) then
        raise ENetHTTPCertificateException.CreateRes(@SNetHttpServerCertificateNotAccepted)
      else
      if (LastError = ERROR_WINHTTP_OPERATION_CANCELLED) or TWinHTTPRequest(ARequest).FCancelled then
        Exit(TExecutionResult.Success)
      else
      begin
        Ex := ENetHTTPClientException.CreateResFmt(AErrorMsg, [LastError, SysErrorMessage(LastError, TWinHttpLib.Handle)]);
        Ex.Data['WinHTTPCode'] := LastError;
        raise Ex;
      end;
  end;
end;

捕获时读取:

except
  on E: ENetHTTPClientException do begin
    if E.Data.ContainsKey('WinHTTPCode') then
      lasterror := Cardinal(E.Data['WinHTTPCode'])
    else
      lasterror := 0;
    ShowMessage(IntToStr(lasterror));
  end;
end;

3. 直接调用WinHTTP原生API

若无需依赖THTTPClient的封装,可直接调用WinHTTP原生API,每次调用后直接获取错误码:

var
  hSession, hConnect, hRequest: HINTERNET;
  LastError: Cardinal;
begin
  hSession := WinHttpOpen(nil, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
  if hSession = nil then
  begin
    LastError := GetLastError;
    ShowMessage(IntToStr(LastError));
    Exit;
  end;
  try
    hConnect := WinHttpConnect(hSession, PChar('example.com'), INTERNET_DEFAULT_HTTPS_PORT, 0);
    if hConnect = nil then
    begin
      LastError := GetLastError;
      ShowMessage(IntToStr(LastError));
      Exit;
    end;
    try
      hRequest := WinHttpOpenRequest(hConnect, 'POST', PChar('/path'), nil, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);
      if hRequest = nil then
      begin
        LastError := GetLastError;
        ShowMessage(IntToStr(LastError));
        Exit;
      end;
      try
        if not WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, nil, 0, 0, 0) then
        begin
          LastError := GetLastError;
          ShowMessage(IntToStr(LastError)); // 直接获取错误码如12002
        end;
      finally
        WinHttpCloseHandle(hRequest);
      end;
    finally
      WinHttpCloseHandle(hConnect);
    end;
  finally
    WinHttpCloseHandle(hSession);
  end;
end;

为什么GetLastError返回0?

GetLastError的错误码存储在线程局部变量中,TWinHTTPClient构造异常对象的过程中可能调用了其他Win32 API,覆盖了原始错误码。等到进入except块调用GetLastError时,原始错误码已被清除或覆盖,因此返回0。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 18:17:05