This is an automated email from the ASF dual-hosted git repository. jensg pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/thrift.git
commit 83ff75377706eee2033f8de0208be2a61de5fbc6 Author: Jens Geyer <[email protected]> AuthorDate: Thu Jun 6 22:46:03 2019 +0200 THRIFT-4882 Autodetect proxy settings with WinHTTP Client: Delphi Patch: Jens Geyer --- lib/delphi/src/Thrift.Transport.MsxmlHTTP.pas | 4 +- lib/delphi/src/Thrift.Transport.WinHTTP.pas | 15 +- lib/delphi/src/Thrift.Utils.pas | 3 + lib/delphi/src/Thrift.WinHTTP.pas | 219 +++++++++++++++++++++++++- lib/delphi/test/TestClient.pas | 26 ++- 5 files changed, 253 insertions(+), 14 deletions(-) diff --git a/lib/delphi/src/Thrift.Transport.MsxmlHTTP.pas b/lib/delphi/src/Thrift.Transport.MsxmlHTTP.pas index 620beba..c666e7f 100644 --- a/lib/delphi/src/Thrift.Transport.MsxmlHTTP.pas +++ b/lib/delphi/src/Thrift.Transport.MsxmlHTTP.pas @@ -121,8 +121,8 @@ begin then srvHttp.setTimeouts( DnsResolveTimeout, ConnectionTimeout, SendTimeout, ReadTimeout); Result.open('POST', FUri, False, '', ''); - Result.setRequestHeader( 'Content-Type', 'application/x-thrift'); - Result.setRequestHeader( 'Accept', 'application/x-thrift'); + Result.setRequestHeader( 'Content-Type', THRIFT_MIMETYPE); + Result.setRequestHeader( 'Accept', THRIFT_MIMETYPE); Result.setRequestHeader( 'User-Agent', 'Delphi/IHTTPClient'); for pair in FCustomHeaders do begin diff --git a/lib/delphi/src/Thrift.Transport.WinHTTP.pas b/lib/delphi/src/Thrift.Transport.WinHTTP.pas index 8b4a7bc..48b74a6 100644 --- a/lib/delphi/src/Thrift.Transport.WinHTTP.pas +++ b/lib/delphi/src/Thrift.Transport.WinHTTP.pas @@ -139,22 +139,25 @@ var begin url := TWinHTTPUrlImpl.Create( FUri); - session := TWinHTTPSessionImpl.Create('Apache Thrift Delphi Client'); + session := TWinHTTPSessionImpl.Create('Apache Thrift Delphi WinHTTP'); session.EnableSecureProtocols( SecureProtocolsAsWinHTTPFlags); connect := session.Connect( url.HostName, url.Port); sPath := url.UrlPath + url.ExtraInfo; - result := connect.OpenRequest( (url.Scheme = 'https'), 'POST', sPath, 'application/x-thrift'); + result := connect.OpenRequest( (url.Scheme = 'https'), 'POST', sPath, THRIFT_MIMETYPE); // setting a timeout value to 0 (zero) means "no timeout" for that setting result.SetTimeouts( DnsResolveTimeout, ConnectionTimeout, SendTimeout, ReadTimeout); - result.AddRequestHeader( 'Content-Type: application/x-thrift', WINHTTP_ADDREQ_FLAG_ADD); - + // headers + result.AddRequestHeader( 'Content-Type: '+THRIFT_MIMETYPE, WINHTTP_ADDREQ_FLAG_ADD); for pair in FCustomHeaders do begin Result.AddRequestHeader( pair.Key +': '+ pair.Value, WINHTTP_ADDREQ_FLAG_ADD); end; + + // AutoProxy support + result.TryAutoProxy( FUri); end; @@ -290,11 +293,11 @@ begin // send all data immediately, since we have it in memory if not http.SendRequest( pData, len, 0) - then raise TTransportExceptionUnknown.Create('send request error'); + then raise TTransportExceptionUnknown.Create('send request error '+IntToStr(GetLastError)); // end request and start receiving if not http.FlushAndReceiveResponse - then raise TTransportExceptionInterrupted.Create('flush/receive error'); + then raise TTransportExceptionInterrupted.Create('flush/receive error '+IntToStr(GetLastError)); FInputStream := THTTPResponseStream.Create(http); end; diff --git a/lib/delphi/src/Thrift.Utils.pas b/lib/delphi/src/Thrift.Utils.pas index 11c4f3e..ede2656 100644 --- a/lib/delphi/src/Thrift.Utils.pas +++ b/lib/delphi/src/Thrift.Utils.pas @@ -93,6 +93,9 @@ type end; +const + THRIFT_MIMETYPE = 'application/x-thrift'; + {$IFDEF Win64} function InterlockedExchangeAdd64( var Addend : Int64; Value : Int64) : Int64; {$ENDIF} diff --git a/lib/delphi/src/Thrift.WinHTTP.pas b/lib/delphi/src/Thrift.WinHTTP.pas index 4b98f69..b26f6ba 100644 --- a/lib/delphi/src/Thrift.WinHTTP.pas +++ b/lib/delphi/src/Thrift.WinHTTP.pas @@ -63,6 +63,40 @@ type LPURL_COMPONENTSW = LPURL_COMPONENTS; + // When retrieving proxy data, an application must free the lpszProxy and + // lpszProxyBypass strings contained in this structure (if they are non-NULL) + // using the GlobalFree function. + LPWINHTTP_PROXY_INFO = ^WINHTTP_PROXY_INFO; + WINHTTP_PROXY_INFO = record + dwAccessType : DWORD; // see WINHTTP_ACCESS_* types below + lpszProxy : LPWSTR; // proxy server list + lpszProxyBypass : LPWSTR; // proxy bypass list + end; + + LPWINHTTP_PROXY_INFOW = ^WINHTTP_PROXY_INFOW; + WINHTTP_PROXY_INFOW = WINHTTP_PROXY_INFO; + + + WINHTTP_AUTOPROXY_OPTIONS = record + dwFlags : DWORD; + dwAutoDetectFlags : DWORD; + lpszAutoConfigUrl : LPCWSTR; + lpvReserved : LPVOID; + dwReserved : DWORD; + fAutoLogonIfChallenged : BOOL; + end; + + + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG = record + fAutoDetect : BOOL; + lpszAutoConfigUrl : LPWSTR; + lpszProxy : LPWSTR; + lpszProxyBypass : LPWSTR; + end; + + + + function WinHttpCloseHandle( aHandle : HINTERNET) : BOOL; stdcall; function WinHttpOpen( const pszAgentW : LPCWSTR; @@ -104,6 +138,16 @@ function WinHttpAddRequestHeaders( const hRequest : HINTERNET; const dwModifiers : DWORD ) : BOOL; stdcall; +function WinHttpGetProxyForUrl( const hSession : HINTERNET; + const lpcwszUrl : LPCWSTR; + const options : WINHTTP_AUTOPROXY_OPTIONS; + const info : WINHTTP_PROXY_INFO + ) : BOOL; stdcall; + +function WinHttpGetIEProxyConfigForCurrentUser( var config : WINHTTP_CURRENT_USER_IE_PROXY_CONFIG + ) : BOOL; stdcall; + + function WinHttpSendRequest( const hRequest : HINTERNET; const lpszHeaders : LPCWSTR; const dwHeadersLength : DWORD; @@ -353,6 +397,17 @@ const or WINHTTP_FLAG_SECURE_PROTOCOL_SSL3 or WINHTTP_FLAG_SECURE_PROTOCOL_TLS1; + // AutoProxy + WINHTTP_AUTOPROXY_AUTO_DETECT = $00000001; + WINHTTP_AUTOPROXY_CONFIG_URL = $00000002; + WINHTTP_AUTOPROXY_HOST_KEEPCASE = $00000004; + WINHTTP_AUTOPROXY_HOST_LOWERCASE = $00000008; + WINHTTP_AUTOPROXY_RUN_INPROCESS = $00010000; + WINHTTP_AUTOPROXY_RUN_OUTPROCESS_ONLY = $00020000; + + // Flags for dwAutoDetectFlags + WINHTTP_AUTO_DETECT_TYPE_DHCP = $00000001; + WINHTTP_AUTO_DETECT_TYPE_DNS_A = $00000002; const WINHTTP_ERROR_BASE = 12000; @@ -417,11 +472,16 @@ const type + IWinHTTPSession = interface; + IWinHTTPConnection = interface; + IWinHTTPRequest = interface - ['{35C6D9D4-FDCE-42C6-B84C-9294E6FB904C}'] + ['{0B7D095E-BB3D-4444-8686-5536E7D6437B}'] function Handle : HINTERNET; + function Connection : IWinHTTPConnection; function AddRequestHeader( const aHeader : string; const addflag : DWORD = WINHTTP_ADDREQ_FLAG_ADD) : Boolean; function SetTimeouts( const aResolveTimeout, aConnectTimeout, aSendTimeout, aReceiveTimeout : Int32) : Boolean; + procedure TryAutoProxy( const aUrl : string); function SendRequest( const pBuf : Pointer; const dwBytes : DWORD; const dwExtra : DWORD = 0) : Boolean; function WriteExtraData( const pBuf : Pointer; const dwBytes : DWORD) : DWORD; function FlushAndReceiveResponse : Boolean; @@ -430,8 +490,9 @@ type end; IWinHTTPConnection = interface - ['{1C4F78B5-1525-4788-B638-A0E41BCF4D43}'] + ['{ED5BCA49-84D6-4CFE-BF18-3238B1FF2AFB}'] function Handle : HINTERNET; + function Session : IWinHTTPSession; function OpenRequest( const secure : Boolean; const aVerb, aObjName, aAcceptTypes : UnicodeString) : IWinHTTPRequest; end; @@ -519,6 +580,7 @@ type // IWinHTTPConnection function OpenRequest( const secure : Boolean; const aVerb, aObjName, aAcceptTypes : UnicodeString) : IWinHTTPRequest; + function Session : IWinHTTPSession; public constructor Create( const aSession : IWinHTTPSession; const aHostName : UnicodeString; const aPort : INTERNET_PORT); @@ -533,8 +595,10 @@ type FConnection : IWinHTTPConnection; // IWinHTTPRequest + function Connection : IWinHTTPConnection; function AddRequestHeader( const aHeader : string; const addflag : DWORD = WINHTTP_ADDREQ_FLAG_ADD) : Boolean; function SetTimeouts( const aResolveTimeout, aConnectTimeout, aSendTimeout, aReceiveTimeout : Int32) : Boolean; + procedure TryAutoProxy( const aUrl : string); function SendRequest( const pBuf : Pointer; const dwBytes : DWORD; const dwExtra : DWORD = 0) : Boolean; function WriteExtraData( const pBuf : Pointer; const dwBytes : DWORD) : DWORD; function FlushAndReceiveResponse : Boolean; @@ -595,6 +659,18 @@ type end; + WINHTTP_PROXY_INFO_Helper = record helper for WINHTTP_PROXY_INFO + procedure Initialize; + procedure FreeAllocatedResources; + end; + + + WINHTTP_CURRENT_USER_IE_PROXY_CONFIG_Helper = record helper for WINHTTP_CURRENT_USER_IE_PROXY_CONFIG + procedure Initialize; + procedure FreeAllocatedResources; + end; + + EWinHTTPException = class(Exception); implementation @@ -610,6 +686,8 @@ function WinHttpSetTimeouts; stdcall; external WINHTTP_DLL; function WinHttpQueryOption; stdcall; external WINHTTP_DLL; function WinHttpSetOption; stdcall; external WINHTTP_DLL; function WinHttpAddRequestHeaders; stdcall; external WINHTTP_DLL; +function WinHttpGetProxyForUrl; stdcall; external WINHTTP_DLL; +function WinHttpGetIEProxyConfigForCurrentUser; stdcall; external WINHTTP_DLL; function WinHttpWriteData; stdcall; external WINHTTP_DLL; function WinHttpReceiveResponse; stdcall; external WINHTTP_DLL; function WinHttpQueryHeaders; stdcall; external WINHTTP_DLL; @@ -619,6 +697,51 @@ function WinHttpCrackUrl; stdcall; external WINHTTP_DLL; function WinHttpCreateUrl; stdcall; external WINHTTP_DLL; +{ misc. record helper } + + +procedure GlobalFreeAndNil( var p : LPWSTR); +begin + if p <> nil then begin + GlobalFree( HGLOBAL( p)); + p := nil; + end; +end; + + +procedure WINHTTP_PROXY_INFO_Helper.Initialize; +begin + FillChar( Self, SizeOf(Self), 0); +end; + + +procedure WINHTTP_PROXY_INFO_Helper.FreeAllocatedResources; +// The caller must free the lpszProxy and lpszProxyBypass strings +// if they are non-NULL. Use GlobalFree to free the strings. +begin + GlobalFreeAndNil( lpszProxy); + GlobalFreeAndNil( lpszProxyBypass); + Initialize; +end; + + +procedure WINHTTP_CURRENT_USER_IE_PROXY_CONFIG_Helper.Initialize; +begin + FillChar( Self, SizeOf(Self), 0); +end; + + +procedure WINHTTP_CURRENT_USER_IE_PROXY_CONFIG_Helper.FreeAllocatedResources; +// The caller must free the lpszProxy, lpszProxyBypass and lpszAutoConfigUrl strings +// if they are non-NULL. Use GlobalFree to free the strings. +begin + GlobalFreeAndNil( lpszProxy); + GlobalFreeAndNil( lpszProxyBypass); + GlobalFreeAndNil( lpszAutoConfigUrl); + Initialize; +end; + + { TWinHTTPHandleObjectImpl } constructor TWinHTTPHandleObjectImpl.Create( const aHandle : HINTERNET); @@ -713,6 +836,12 @@ begin end; +function TWinHTTPConnectionImpl.Session : IWinHTTPSession; +begin + result := FSession; +end; + + function TWinHTTPConnectionImpl.OpenRequest( const secure : Boolean; const aVerb, aObjName, aAcceptTypes : UnicodeString) : IWinHTTPRequest; var dwFlags : DWORD; begin @@ -759,6 +888,12 @@ begin end; +function TWinHTTPRequestImpl.Connection : IWinHTTPConnection; +begin + result := FConnection; +end; + + function TWinHTTPRequestImpl.SetTimeouts( const aResolveTimeout, aConnectTimeout, aSendTimeout, aReceiveTimeout : Int32) : Boolean; begin result := WinHttpSetTimeouts( FHandle, aResolveTimeout, aConnectTimeout, aSendTimeout, aReceiveTimeout); @@ -771,6 +906,85 @@ begin end; +procedure TWinHTTPRequestImpl.TryAutoProxy( const aUrl : string); +// From MSDN: +// AutoProxy support is not fully integrated into the HTTP stack in WinHTTP. +// Before sending a request, the application must call WinHttpGetProxyForUrl +// to obtain the name of a proxy server and then call WinHttpSetOption using +// WINHTTP_OPTION_PROXY to set the proxy configuration on the WinHTTP request +// handle created by WinHttpOpenRequest. +// See https://docs.microsoft.com/en-us/windows/desktop/winhttp/winhttp-autoproxy-api +var + options : WINHTTP_AUTOPROXY_OPTIONS; + proxy : WINHTTP_PROXY_INFO; + ieProxy : WINHTTP_CURRENT_USER_IE_PROXY_CONFIG; + dwSize : DWORD; +begin + // try AutoProxy via PAC first + proxy.Initialize; + try + FillChar( options, SizeOf(options), 0); + options.dwFlags := WINHTTP_AUTOPROXY_AUTO_DETECT; + options.dwAutoDetectFlags := WINHTTP_AUTO_DETECT_TYPE_DHCP or WINHTTP_AUTO_DETECT_TYPE_DNS_A; + options.fAutoLogonIfChallenged := TRUE; + if WinHttpGetProxyForUrl( FConnection.Session.Handle, PChar(aUrl), options, proxy) then begin + dwSize := SizeOf(proxy); + WinHttpSetOption( Handle, WINHTTP_OPTION_PROXY, @proxy, dwSize); + Exit; + end; + + finally + proxy.FreeAllocatedResources; + end; + + // Use IE settings as a fallback, useful in client (i.e. non-server) environments + ieProxy.Initialize; + try + if WinHttpGetIEProxyConfigForCurrentUser( ieProxy) + then begin + + // lpszAutoConfigUrl = "Use automatic proxy configuration" + if ieProxy.lpszAutoConfigUrl <> nil then begin + options.lpszAutoConfigUrl := ieProxy.lpszAutoConfigUrl; + options.dwFlags := options.dwFlags or WINHTTP_AUTOPROXY_CONFIG_URL; + + proxy.Initialize; + try + if WinHttpGetProxyForUrl( FConnection.Session.Handle, PChar(aUrl), options, proxy) then begin + dwSize := SizeOf(proxy); + WinHttpSetOption( Handle, WINHTTP_OPTION_PROXY, @proxy, dwSize); + Exit; + end; + finally + proxy.FreeAllocatedResources; + end; + end; + + // lpszProxy = "use a proxy server" + if ieProxy.lpszProxy <> nil then begin + proxy.Initialize; + try + proxy.dwAccessType := WINHTTP_ACCESS_TYPE_NAMED_PROXY; + proxy.lpszProxy := ieProxy.lpszProxy; + proxy.lpszProxyBypass := ieProxy.lpszProxyBypass; + dwSize := SizeOf(proxy); + WinHttpSetOption( Handle, WINHTTP_OPTION_PROXY, @proxy, dwSize); + Exit; + finally + proxy.Initialize; // not FreeAllocatedResources, we only hold pointer copies! + end; + end; + + end; + + finally + ieProxy.FreeAllocatedResources; + end; +end; + + + + function TWinHTTPRequestImpl.SendRequest( const pBuf : Pointer; const dwBytes, dwExtra : DWORD) : Boolean; begin result := WinHttpSendRequest( FHandle, @@ -979,3 +1193,4 @@ end; end. + diff --git a/lib/delphi/test/TestClient.pas b/lib/delphi/test/TestClient.pas index ebda7c6..677d416 100644 --- a/lib/delphi/test/TestClient.pas +++ b/lib/delphi/test/TestClient.pas @@ -50,6 +50,7 @@ uses Thrift.Transport, Thrift.Stream, Thrift.Test, + Thrift.WinHTTP, Thrift.Utils, Thrift.Collections; @@ -1322,7 +1323,9 @@ end; function TClientThread.InitializeHttpTransport( const aTimeoutSetting : Integer) : IHTTPClient; -var sUrl : string; +var sUrl : string; + comps : URL_COMPONENTS; + dwChars : DWORD; begin ASSERT( FSetup.endpoint in [trns_MsxmlHttp, trns_WinHttp]); @@ -1332,12 +1335,27 @@ begin sUrl := sUrl + FSetup.host; + // add the port number if necessary and at the right place + FillChar( comps, SizeOf(comps), 0); + comps.dwStructSize := SizeOf(comps); + comps.dwSchemeLength := MAXINT; + comps.dwHostNameLength := MAXINT; + comps.dwUserNameLength := MAXINT; + comps.dwPasswordLength := MAXINT; + comps.dwUrlPathLength := MAXINT; + comps.dwExtraInfoLength := MAXINT; + Win32Check( WinHttpCrackUrl( PChar(sUrl), Length(sUrl), 0, comps)); case FSetup.port of - 80 : if FSetup.useSSL then sUrl := sUrl + ':'+ IntToStr(FSetup.port); - 443 : if not FSetup.useSSL then sUrl := sUrl + ':'+ IntToStr(FSetup.port); + 80 : if FSetup.useSSL then comps.nPort := FSetup.port; + 443 : if not FSetup.useSSL then comps.nPort := FSetup.port; else - if FSetup.port > 0 then sUrl := sUrl + ':'+ IntToStr(FSetup.port); + if FSetup.port > 0 then comps.nPort := FSetup.port; end; + dwChars := Length(sUrl) + 64; + SetLength( sUrl, dwChars); + Win32Check( WinHttpCreateUrl( comps, 0, @sUrl[1], dwChars)); + SetLength( sUrl, dwChars); + Console.WriteLine('Target URL: '+sUrl); case FSetup.endpoint of
