Einzelnen Beitrag anzeigen

Bbommel

Registriert seit: 27. Jun 2007
Ort: Köln
655 Beiträge
 
Delphi 12 Athens
 
#1

Proxy-Erkennung für REST-Client/HTTP-Client nicht korrekt

  Alt 16. Mai 2022, 10:43
Hallo zusammen,

einer meiner Kunden hatte Probleme, mit unserer Anwendung auf eine REST-API zuzugreifen. Nachdem ich zunächst behauptet hatte, dass da irgendwas bei ihm an Firewalls oder Proxys falsch konfiguriert ist (weil wir das Problem bisher auch nie hatten), muss ich mittlerweile zugeben, dass er wohl recht hat und das Problem tatsächlich bei uns liegt. Das Problem ist, dass der Kunde tatsächlich einen Proxy im Netzwerk aktiv hat, Windows komplett auf "automatische Erkennung" eingestellt ist, aber Delphi diese automatische Erkennung nicht korrekt durchführt. Bevor ich jetzt einen Bugreport im QP aufmache, wollte ich hier noch einmal hören, ob ich vielleicht etwas übersehe, und auch, ob ihr vielleicht einen besseren Tipp für einen Workaround oder auch für die "richtige" Lösung habt.

Das Problem besteht nach einem ersten Drüberschauen zumindest in Delphi 10.3 bis zum aktuellen 11.1 und liegt in der System.Net.HttpClient.Win. Dort gibt es die Methode TWinHTTPRequest.SetWinProxySettings und diese hat wiederum die Unter-Funktion GetProxyInfo, welche nach meiner Analyse fehlerhaft ist:

Delphi-Quellcode:
  function GetProxyInfo(const AURL: string; var AProxy, AProxyBypass: string): Boolean;
  var
    LAutoDetectProxy: Boolean;
    LpProxyConfig: PWinHTTPCurrentUserIEProxyConfig;
    LWinHttpProxyInfo: TWinHTTPProxyInfo;
    LAutoProxyOptions: TWinHTTPAutoProxyOptions;
  begin
    Result := True;
    AProxy := '';
    AProxyBypass := '';
    FillChar(LAutoProxyOptions, SizeOf(LAutoProxyOptions), 0);

    LpProxyConfig := TWinHttpLib.GetProxyConfig;
    if LpProxyConfig <> nil then
    begin
      if LpProxyConfig^.fAutoDetect then
      begin
        LAutoProxyOptions.dwFlags := WINHTTP_AUTOPROXY_AUTO_DETECT;
        LAutoProxyOptions.dwAutoDetectFlags := WINHTTP_AUTO_DETECT_TYPE_DHCP or WINHTTP_AUTO_DETECT_TYPE_DNS_A;
        // *** Obwohl die AutoDetect in der Proxy-Konfig aktiv ist, bleibt LAutoDetectProxy auf False! ***
      end;

      if LpProxyConfig^.lpszAutoConfigURL <> 'then
      begin
        LAutoProxyOptions.dwFlags := WINHTTP_AUTOPROXY_CONFIG_URL;
        LAutoProxyOptions.lpszAutoConfigUrl := LpProxyConfig^.lpszAutoConfigUrl;
        LAutoDetectProxy := True;
      end
      else
      begin
        // *** Wenn keine "AutoConfig"-URL angegeben ist, landet der Code hier,
        // *** auch wenn in der ProxyConfig "AutoDetect" angegeben ist. Dadurch
        // *** wird dann versucht, einen direkt eingestellten Proxy zu nehmen,
        // *** auch wenn keiner eingestellt ist. LAutoDetectProxy bleibt auf False.
        AProxy := LpProxyConfig^.lpszProxy;
        AProxyBypass := LpProxyConfig^.lpszProxyBypass;
        LAutoDetectProxy := False;
      end;
    end
    else
    begin
      // if the proxy configuration is not found then try to autodetect it (If the Internet Explorer settings are not configured for system accounts)
      LAutoProxyOptions.dwFlags := WINHTTP_AUTOPROXY_AUTO_DETECT;
      LAutoProxyOptions.dwAutoDetectFlags := WINHTTP_AUTO_DETECT_TYPE_DHCP or WINHTTP_AUTO_DETECT_TYPE_DNS_A;
      LAutoDetectProxy := True;
    end;

    if (AProxy = '') and LAutoDetectProxy then
    begin
      // *** Eigentlich müssten wir hier landen, aber wegen der falschen Abfragen oben, landen wir
      // *** nie hier. Folge: die entscheidende Funktion aus der WinHTTP-API, nämlich
      // *** WinHttpGetProxyForUrl wird nie aufgerufen!

      // From https://msdn.microsoft.com/en-us/library/aa383153%28VS.85%29.aspx
      // Try with fAutoLogonIfChallenged parameter set to false, if ERROR_WINHTTP_LOGIN_FAILURE then try
      // with fAutoLogonIfChallenged parameter set to true.
      LAutoProxyOptions.fAutoLogonIfChallenged := False;
      if WinHttpGetProxyForUrl(LClient.FWSession, LPCWSTR(AURL), LAutoProxyOptions, LWinHttpProxyInfo) then
      begin
        AProxy := LWinHttpProxyInfo.lpszProxy;
        AProxyBypass := LWinHttpProxyInfo.lpszProxyBypass;
      end
      else
      begin
        if GetLastError = ERROR_WINHTTP_LOGIN_FAILURE then
        begin
          LAutoProxyOptions.fAutoLogonIfChallenged := True;
          if WinHttpGetProxyForUrl(LClient.FWSession, LPCWSTR(AURL), LAutoProxyOptions, LWinHttpProxyInfo) then
          begin
            AProxy := LWinHttpProxyInfo.lpszProxy;
            AProxyBypass := LWinHttpProxyInfo.lpszProxyBypass;
          end
          else
            Result := False;
        end
        else
          Result := False;
      end;
    end;
    if AProxy = 'then
      Result := False;
  end;
Meine Kommentare mit den *** sollten anzeigen, wo meiner Meinung nach der Fehler liegt und weshalb die automatische Proxy-Erkennung nicht funktioniert.

Eine einfache Lösung sieht so aus:
Delphi-Quellcode:
  function GetProxyInfo(const AURL: string; var AProxy, AProxyBypass: string): Boolean;
  var
    LAutoDetectProxy: Boolean;
    LpProxyConfig: PWinHTTPCurrentUserIEProxyConfig;
    LWinHttpProxyInfo: TWinHTTPProxyInfo;
    LAutoProxyOptions: TWinHTTPAutoProxyOptions;
  begin
    Result := True;
    AProxy := '';
    AProxyBypass := '';
    FillChar(LAutoProxyOptions, SizeOf(LAutoProxyOptions), 0);

    LpProxyConfig := TWinHttpLib.GetProxyConfig;
    if LpProxyConfig <> nil then
    begin
      if LpProxyConfig^.fAutoDetect then
      begin
        LAutoProxyOptions.dwFlags := WINHTTP_AUTOPROXY_AUTO_DETECT;
        LAutoProxyOptions.dwAutoDetectFlags := WINHTTP_AUTO_DETECT_TYPE_DHCP or WINHTTP_AUTO_DETECT_TYPE_DNS_A;
      end;

      if LpProxyConfig^.lpszAutoConfigURL <> 'then
      begin
        LAutoProxyOptions.dwFlags := WINHTTP_AUTOPROXY_CONFIG_URL;
        LAutoProxyOptions.lpszAutoConfigUrl := LpProxyConfig^.lpszAutoConfigUrl;
        LAutoDetectProxy := True;
      end
      else
      begin
        AProxy := LpProxyConfig^.lpszProxy;
        AProxyBypass := LpProxyConfig^.lpszProxyBypass;
        // *** hier die einfache Änderung: haben wir keinen manuellen Proxy bekommen,
        // *** dann sorgen wir hier dafür, dass wir später in dem Block landen, in dem
        // *** GetProxyForURL aufgerufen wird
        LAutoDetectProxy := AProxy = '';
      end;
    end
    else
  [... weiter wie oben ...]
Mit dieser einfachen Änderung funktioniert es tatsächlich beim Kunden. "Richtiger" wäre wahrscheinlich etwas in diese Richtung:

Delphi-Quellcode:
  function GetProxyInfo(const AURL: string; var AProxy, AProxyBypass: string): Boolean;
  var
    LAutoDetectProxy: Boolean;
    LpProxyConfig: PWinHTTPCurrentUserIEProxyConfig;
    LWinHttpProxyInfo: TWinHTTPProxyInfo;
    LAutoProxyOptions: TWinHTTPAutoProxyOptions;
  begin
    Result := True;
    AProxy := '';
    AProxyBypass := '';
    FillChar(LAutoProxyOptions, SizeOf(LAutoProxyOptions), 0);

    LpProxyConfig := TWinHttpLib.GetProxyConfig;
    if LpProxyConfig <> nil then
    begin
      // *** AutoDetect mal klar initialisieren
      LAutoDetectProxy := False;
      if LpProxyConfig^.fAutoDetect then
      begin
        LAutoProxyOptions.dwFlags := WINHTTP_AUTOPROXY_AUTO_DETECT;
        LAutoProxyOptions.dwAutoDetectFlags := WINHTTP_AUTO_DETECT_TYPE_DHCP or WINHTTP_AUTO_DETECT_TYPE_DNS_A;
        // *** AutoDetect hier auch tatsächlich aktivieren, wenn es in der Config so steht!
        LAutoDetectProxy := True;
      end;

      if LpProxyConfig^.lpszAutoConfigURL <> 'then
      begin
        LAutoProxyOptions.dwFlags := WINHTTP_AUTOPROXY_CONFIG_URL;
        LAutoProxyOptions.lpszAutoConfigUrl := LpProxyConfig^.lpszAutoConfigUrl;
        LAutoDetectProxy := True;
      end
      // *** Proxy explizit setzen, wenn wirklich keine AutoDetect aktiviert
      else if not LAutoDetectProxy then
      begin
        AProxy := LpProxyConfig^.lpszProxy;
        AProxyBypass := LpProxyConfig^.lpszProxyBypass;
      end;
    end
    else
  [... weiter wie oben ...]
Soweit, das, was ich rausgefunden habe. Ist für mich immer recht schwer zu testen, weil ich im Büro bei mir ohne Proxy arbeite, aber das Ergebnis vom Kunden war mit der Änderung (und ein paar Debug-Ausgaben) schon sehr eindeutig. Oder übersehe ich etwas?

Als Workaround bis das von Emba mal gefixt wird, fällt mir nur ein, die System.Net.HttpClient.Win lokal ins Projekt einzubauen und die Stellen wie oben angegeben zu korrigieren. Hat hier jemand eine bessere Idee?
  Mit Zitat antworten Zitat