如何无需解析错误字符串从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
相关产品推荐
相关产品推荐

