You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@thrift.apache.org by je...@apache.org on 2019/06/07 20:27:20 UTC

[thrift] 02/02: THRIFT-4882 Autodetect proxy settings with WinHTTP Client: Delphi Patch: Jens Geyer

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 <je...@apache.org>
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