Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Netzwerke (https://www.delphipraxis.net/14-netzwerke/)
-   -   Probleme mit SOAP von 10.2.3 nach 10.3.1 (https://www.delphipraxis.net/201637-probleme-mit-soap-von-10-2-3-nach-10-3-1-a.html)

Bbommel 9. Aug 2019 10:04


Probleme mit SOAP von 10.2.3 nach 10.3.1
 
Hi,

weiß jemand, ob es bekannte Probleme mit REST in Rio 10.3.1 gibt, die es in Tokyo 10.2.3 noch nicht gab? Ich meine, ich hätte hier in den letzten Monaten auch mal entsprechende Berichte/Fragen vorbeirauschen sehen, konnte bei der Suche aber nichts finden.

Zunächst: auf meinem eigenen System bzw. auf einem Testsystem bei einem Partnerunternehmen funktioniert alles problemlos sowohl mit einer mit 10.2.3 als auch mit 10.3.1 erstellten Version des Programms.

Bei einem Kunden erscheint aber bei der mit 10.3.1 erzeugten Version nur folgende Fehlermeldung: "Fehler [Leeres Dokument]". Das heißt, ich bekomme hier offenbar gar keine Antwort. Kompiliere ich den aktuellen Programmstand mit 10.2.3 und schicke das dem Kunden, kann er wieder problemlos arbeiten.

Gibt es hier irgendwelche bekannten Probleme? Ich habe gesehen, dass die REST-Bibliothek im Hintergrund wohl ziemlich umgebaut wurde. Hatte jemand schon ein ähnliches Problem und das gelöst? Oder ist es sogar ein Bug, der ein 10.3.2 gefixt wurde?

Bbommel 3. Sep 2019 15:54

AW: Probleme mit REST von 10.2.3 nach 10.3.1
 
Ein kleines Update zum Thema. Leider ist es noch nicht komplett gelöst, aber etwas weiter bin ich schon.

Mit dem Wechsel von 10.2.3 auf 10.3.1 wurden die SOAP-Bibliotheken von Delphi im Hintergrund für die Übertragung von WinInet auf die WinHttp-Api von Windows umgestellt. Dabei sind leider gleich mehrere Sachen kaputtgegangen, die aber immerhin zum Teil in 10.3.2 schon wieder repariert wurden.

Das erste: die Fehlermeldungen, die man bei Problemen mit SOAP in 10.3.1 bekommen hat, waren selbst leider auch fehlerhaft. Es kam die besagte Fehlermeldung "Leeres Dokument", welche ja nicht wirklich weiterhilft. Nach einem Update auf 10.3.2 erhält man eine hilfreichere Fehlermeldung: 401 Unauthorized.

Das führt zum zweiten (eigentlichen) Problem: Wenn der Web Service in einem lokalen Netz auf einem Windows-Server läuft, dann war es bisher möglich, sich mit den aktuellen Windows Credentials anzumelden (NTLM) - man musste nicht seperat noch einmal Benutzername und Passwort speichern. Diese Funktion ist auf dem Weg von 10.2.3 nach 10.3.1 ebenfalls kaputtgegangen, offenbar aber durch ein Problem in der WinHttp-Api, nicht in Delphi direkt (hier beschreibt ein User das Problem und einen möglichen Workaround bei Stackoverflow). In Delphi 10.3.2 gibt es eine neue Propperty im HTTPWebNode vom HTTPRio, nämlich "UseDefaultCredentials", welche genau das macht, was der User auf Stackoverflow als Patch vorgeschlagen hat. Bei einem ersten kurzen Test beim Kunden hat es allerdings leider noch nicht geholfen - ich bekomme weiterhin einen 401.

Wenn ich Benutzername und Pssswort manuell in meinem Programm angebe und damit die HTTPWebNode-Eigennschaften Username und Password setze, dann funktioniert übrigens in allen Delphi-Versionen (10.2.3, 10.3.1, 10.3.2) alles problemlos. Aber alle Login-Daten, die ich nicht speichern muss, finde ich gut und würde daher eigentlich gerne weiter die Windows-Anmeldung benutzen, was zumindest ja auch in 10.3.2 auch wieder funktionieren müsste.

Wenn jemand dazu also noch etwas weiß, dann gerne immer her mit Tipps und Infos. Ansonsten schreibe ich hier irgendwann mal drunter, wenn ich eine endgültige Lösung gefunden haben sollte.

TiGü 4. Sep 2019 13:15

AW: Probleme mit REST von 10.2.3 nach 10.3.1
 
Vielen Dank für das Update. Das hilft gewiss den ein oder anderen zukünftigen Suchenden! :thumb:

Bbommel 4. Sep 2019 13:20

AW: Probleme mit SOAP von 10.2.3 nach 10.3.1
 
Gerade erst gemerkt: kann vielleicht mal ein Mod den Titel des Themas von "REST" nach "SOAP" ändern? Ich Dödel. :wall:

Bbommel 12. Sep 2019 11:40

AW: Probleme mit SOAP von 10.2.3 nach 10.3.1
 
Vorweg die Bitte an einen Moderator: kann einer von euch Mods das Thema von "REST" nach "SOAP" umbenennen, damit es auch von anderen korrekt gefunden wird? Ich hatte damals beim Erstellen offenbar einen kleinen Aussetzer. ;-)

Ich habe die Lösung für das Problem. Leider ist es auch in Delphi Rio 10.3.2 noch immer ein Bug in der Unit System.Net.HttpClient.Win, den man nur gelöst bekommt, indem man eine eigene Version der Unit in sein Projekt einbaut und die entscheidende Stelle patcht. Wer bessere Lösungen hat, gerne melden.

Ich versuche nochmal, das Problem, den Fehler und eine mögliche Lösung kurz zusammenzufassen (dann braucht ihr die bisherigen Beiträge nicht unbedingt lesen):

Problem:
Es soll ein Web Service innerhalb eines Unternehmsnetzwerks genutzt werden. Der Web Service wird auf einem Windows Server gehostet (konkret geht es hier um Web Services von Microsoft Dynamics NAV) und verlangt eine Authentifizierung, damit er genutzt werden kann. Da das ganze innerhalb eines Unternehmensnetzwerk passiert, kann eigentlich NTLM/Kerberos für die Authentifizierung genutzt und dabei die Credentials das aktuellen Benutzers genutzt werden. Heißt vereinfacht: Windows kümmert sich im Hntergrund um die Anmeldung am Web Service und der Benutzer muss seine Login-Daten nicht erneut eingeben bzw. man muss sie nicht selber irgendwo speichern. Bis Delphi 10.2.3 funktionierte das meist problemlos, ab Delphi 10.3.1 hat Emba die ganzen SOAP-Bibliotheken technisch komplett umgebaut, um sie auf THTTPClient umzubauen. Damit wird unter Windows nun nicht mehr die API WinInet, sondern WinHttp benutzt.

Der Fehler:
Wenn WinHttp mit NTLM arbeitet, dann gibt es eine Option, welche steuert, ob es mit den aktuellen Benutzer-Credentials arbeiten soll oder ob man Benutzername/Passwort angeben muss. Diese Option heißt WINHTTP_OPTION_AUTOLOGON_POLICY. Weist man ihr den Wert WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW zu, dann benutzt die Bibliothek den aktuellen Benutzer für den Login und Benutzername/Passwort werden erst gar nicht abgefragt. Weist man der Option hingegen den Wert WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH zu, dann braucht man auf jeden Fall Benutzername/Passwort. In Rio 10.3.1 wurde das überhaupt nicht beachtet, somit war ein Login zum Web Service unter Benutzung des aktuellen Benutzers nicht möglich. In Rio 10.3.2 hat man versucht, das Problem anzugehen und die neue Eigenschaft "useDefaultCredentials" im THTTPClient und im WebNode eingeführt. Dann gibt es folgende Funktion:

Delphi-Quellcode:
procedure TWinHTTPRequest.SetWinLogonPolicy;
var
  LClient: TWinHTTPClient;
  LOption: DWORD;
begin
  LClient := TWinHTTPClient(FClient);
  if LClient.UseDefaultCredentials then
    LOption := WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW
  else
    LOption := WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH;
  WinHttpSetOption(FWRequest, WINHTTP_OPTION_AUTOLOGON_POLICY, @LOption, SizeOf(LOption));
end;
Sieht eigentlich super aus, funktioniert aber nicht. ;-) Das Problem ist, dass die das "UseDefaultCredentials" nicht einfach im THTTPClient speichern, sondern in einem Record, der wiederum irgendwie in einem Dictionary steckt - da war ich dann irgendwann mit meinem Latein oder meiner Geduld am Ende und es mag sich gerne noch mal jemand ansehen. Der Effekt ist jedenfalls der: man weist zwar in seinem Code dem UseDefaultCredentials ein "true" zu, aber wenn dann die Verbindung tatsächlich initialisiert und somit die obige Funktion aufgerufen wird, dann ist es trotzdem immer noch "false". Die Folge: die Option WINHTTP_OPTION_AUTOLOGON_POLICY wird auf den falschen Wert gesetzt und WinHttp erwartet einen Aufruf von "SetCredentials" mit manuell mitgegebenem Benutzernamen und Passwort.

(hässlicher) Workaround:
Aktuell fällt mir nichts besseres ein, als eine eigene Version der Unit in mein Projekt zu kopieren und dort die Funktion wie folgt anzupassen:

Delphi-Quellcode:
procedure TWinHTTPRequest.SetWinLogonPolicy;
var
  LClient: TWinHTTPClient;
  LOption: DWORD;
begin
  LClient := TWinHTTPClient(FClient);
  if LClient.UseDefaultCredentials or forceSecurityLevelLow then
    LOption := WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW
  else
    LOption := WINHTTP_AUTOLOGON_SECURITY_LEVEL_HIGH;
  WriteDebugMessage('SetWinLogonPolicy: call WinHttpSetOption with Option '+IntToStr(LOption));
  WinHttpSetOption(FWRequest, WINHTTP_OPTION_AUTOLOGON_POLICY, @LOption, SizeOf(LOption));
end;
Der entscheidende Unterschied ist nur
Delphi-Quellcode:
if LClient.UseDefaultCredentials or forceSecurityLevelLow then
. Ich habe hier im interface-Teil noch eine globale Variable ergänzt und kann diese nun vom eigentlichen Programm aus auf true setzen, um das zu erreichen, was ja eigentlich "UseDefaultCredentials" selber tun sollte. Dann klappt ein Login am Server, ohne dass man Benutzername/Passwort irgendwo speichern und angeben muss.

Ich werde dann wohl mal einen Eintrag bei QC dazu machen...

TiGü 12. Sep 2019 13:19

AW: Probleme mit SOAP von 10.2.3 nach 10.3.1
 
Sehr gut! Vielen Dank, dass du deine Erkenntnisse für die Nachwelt teilst.

Zusatzidee: Anstatt die ganze Unit zu kopieren, kannst du auch die Windows-Funktion WinHttpSetOption intercepten.
Siehe meine Beiträge hier: https://www.delphipraxis.net/198305-...-tls-win7.html

Bbommel 12. Sep 2019 15:32

AW: Probleme mit SOAP von 10.2.3 nach 10.3.1
 
Danke für den Tipp. Das sieht dann in meiner Testanwendung so aus und funktioniert tatsächlich:
Delphi-Quellcode:
uses
  [...]
  DDetours, Winapi.WinHttp;
[...]

var
  TrampolinWinHttpSetOption: function(hInternet: HINTERNET; dwOption: DWORD;
                   lpBuffer: Pointer; dwBufferLength: DWORD): BOOL; stdcall=nil;

implementation

function InterceptWinHttpSetOption(hInternet: HINTERNET; dwOption: DWORD; lpBuffer: Pointer; dwBufferLength: DWORD): BOOL; stdcall;
var
  LOption: DWORD;
begin
  if dwOption=WINHTTP_OPTION_AUTOLOGON_POLICY then begin
    LOption := WINHTTP_AUTOLOGON_SECURITY_LEVEL_LOW;
    Result:=TrampolinWinHttpSetOption(hInternet, WINHTTP_OPTION_AUTOLOGON_POLICY, @LOption, SizeOf(LOption));
  end else
    Result:=TrampolinWinHttpSetOption(hInternet,dwOption,lpBuffer,dwBufferLength);
end;


{$R *.dfm}

[...]

procedure TmainSoapTest.FormCreate(Sender: TObject);

var
  strList: TStringList;

begin
  if not Assigned(TrampolinWinHttpSetOption) then
  begin
    @TrampolinWinHttpSetOption := InterceptCreate(@WinHttpSetOption, @InterceptWinHttpSetOption);
  end;
  [...]
end;
In einem echten Programm würde man vielleicht nicht ganz so rabiat vorgehen, sondern aus einer Programmkonfiguration abfragen, ob nicht vielleicht doch mit Benutzernamen/Passwort gearbeitet werden soll. Aber das Prinzip funktioniert. :-) Jetzt muss ich mal in mich gehen, ob es mir lieber ist, eine zusätzliche externe Unit einzubinden (also DDetours) oder die ganze HTTClient.Win-Unit rabiat zu überschreiben. Hm. Ist ja hoffentlich nur bis zum nächsten Delphi-Update... Sinnvollere wäre wahrscheinlich wirklich die Sache mit dem Hook.

Hier noch das Ticket bei QC.

TurboMagic 12. Sep 2019 18:37

AW: Probleme mit SOAP von 10.2.3 nach 10.3.1
 
Danke für das Erforschen und Publizieren der Lösung!

Nur die eine Frage bleibt noch offen: ist dieses Problem schon in QP erfasst,
damit es EMBT auch offiziell bekannt ist und hoffentlich in einer Folgeversion
behoben wird?

Bbommel 12. Sep 2019 21:05

AW: Probleme mit SOAP von 10.2.3 nach 10.3.1
 
Zitat:

Zitat von TurboMagic (Beitrag 1445913)
Nur die eine Frage bleibt noch offen: ist dieses Problem schon in QP erfasst, damit es EMBT auch offiziell bekannt ist und hoffentlich in einer Folgeversion behoben wird?

Jepp, steht am Ende meines letzten Posts, also direkt über deinem Beitrag. Ich hab zwar QC statt QP geschrieben, aber das sind doch bürgerliche Kategorien. ;-)

Hier nochmal der Link zum Eintrag im Quality Portal.

TurboMagic 13. Sep 2019 17:29

AW: Probleme mit SOAP von 10.2.3 nach 10.3.1
 
Ja, genaues Lesen hätte mal wieder geholfen ;-)
Danke für's Erfassen des Problems!


Alle Zeitangaben in WEZ +1. Es ist jetzt 00:28 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz