THRIFT-4882 Autodetect proxy settings with WinHTTP
Client: Delphi
Patch: Jens Geyer
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
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 @@
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 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 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 @@
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 @@
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 @@
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.
+
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 @@
Thrift.Transport,
Thrift.Stream,
Thrift.Test,
+ Thrift.WinHTTP,
Thrift.Utils,
Thrift.Collections;
@@ -1322,7 +1323,9 @@
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 @@
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