Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Algorithmen, Datenstrukturen und Klassendesign (https://www.delphipraxis.net/78-algorithmen-datenstrukturen-und-klassendesign/)
-   -   Soap-Service: XML-Fehler (https://www.delphipraxis.net/212377-soap-service-xml-fehler.html)

bcvs 1. Feb 2023 11:39

Soap-Service: XML-Fehler
 
Hallo zusammen,

Ich bastele an einem SOAP-Client (MyDHLAPI von DHL Express).


Beim Aufruf der aus der wdsl importieren Function getRateRequest erhalte ich diesen Fehler:

Zitat:

Ein XML-Dokument muss ein Element der obersten Ebene enthalten.
Zeile: 0
Mein Code sieht so aus:
Delphi-Quellcode:
procedure TDHLExpress.DoRateRequest;
var Request:RateRequest;  // aus der wdsl
    Response:RateResponse; // aus der wdsl
    DHLExpressService:gblExpressRateBook; // aus der wdsl
    DhlExpressHeader:TDHLExpressSoapHeader;
    RIO : THTTPRIO;
begin
  RIO := THTTPRIO.Create(nil);
  RIO.OnAfterExecute :=HTTPRIOAfterExecute;
  RIO.OnBeforeExecute:=HTTPRIOBeforeExecute;

  DHLExpressService:=GetgblExpressRateBook(false, '', RIO);

  DhlExpressHeader :=TDHLExpressSoapHeader.Create; // Für Authentication
  DHLExpressHeader.User:=fUser;
  DHLExpressHeader.Signature:=fSignature;

  Request:=RateRequestCreate; // Hier wird der Request zusammengebaut
  try
    (DHLExpressService as ISOAPHeaders).Send(DHLExpressHeader);
    Response:=DHLExpressService.getRateRequest(Request); // Hier kommt der Fehler
  finally
    Request.Free;
    DhlExpressHeader.Free;
  end;
end;
In HTTPRIOBeforeExecute und HTTPRIOAfterExecute cheche ich, was in SOAPRequest bzw. in SOAPResponse drin steht. SOAPRequest ist in Ordnung, SOAPResponse ist leer.

In einem ähnlichen Fall mit einer anderen API von DHL (nicht Express) funktioniert alles nach diesem Schema bestens.

Was könnte da passiert sein?

Papaschlumpf73 1. Feb 2023 12:55

AW: Soap-Service: XML-Fehler
 
Also der Fehler "Ein XML-Dokument muss ein Element der obersten Ebene enthalten." kommt wahrscheinlich nur, weil der SOAP-Server eine leere bzw. gar keine Antwort gesendet hat. Das ist quasi so, als würde man eine leere Textdatei als XML-Dokument einlesen wollen.

Zur Lösungsfindung würde ich zuerst die komplette Aufruf-URL in den Browser werfen und sehen, ob da eine Antwort kommt.

himitsu 1. Feb 2023 13:02

AW: Soap-Service: XML-Fehler
 
Delphi/Indy-Update?

Ich hab grade den Fall bei einer REST-Komponente.
Alter Code läuft, aber im neuen Delphi nicht.

Die Anfrage/Response ist anders, daher liefert der REST-Server nichts, bzw. was Falsches.

Wir wollen JSON, aber jetzt sendet das Ding teilweise XML zurück, bzw. auch gern mal leere Daten.
Code:
[ XE ]
GET /user/getErpPublicKey HTTP/1.1
Content-Type: application/json; charset=UTF-8
Host: localhost:9999
Accept: text/html, */*
Accept-Encoding: identity
User-Agent: Mozilla/3.0 (compatible; Indy Library)

[ 11 ]
GET /user/getErpPublicKey HTTP/1.1
Content-Type: application/json; charset=UTF-8
Host: localhost:9999
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-Agent: Mozilla/3.0 (compatible; Indy Library)

[ 11 + IdHTTP.Request.Accept := 'application/json'; ]
GET /user/getErpPublicKey HTTP/1.1
Content-Type: application/json; charset=UTF-8
Host: localhost:9999
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-Agent: Mozilla/3.0 (compatible; Indy Library)

Delphi.Narium 1. Feb 2023 13:42

AW: Soap-Service: XML-Fehler
 
Bei der Arbeit mit Indy hilft es kolossal hier
Delphi-Quellcode:
User-Agent: Mozilla/3.0 (compatible; Indy Library)
was aktuelleres zuzuweisen.

Z. B. das, was Dir diese Seite, aus Deinem Lieblingsbrowser aufgerufen, anzeigt.

Etliche Webserver liefern beim "OriginalIndyUserAgent" keine Antwort oder nur, dass ihnen der Browser etwas zu alt ist und man doch lieber was Neueres nehmen sollte.

mjustin 1. Feb 2023 14:12

AW: Soap-Service: XML-Fehler
 
Zitat:

Zitat von bcvs (Beitrag 1518141)

Zitat:

Ein XML-Dokument muss ein Element der obersten Ebene enthalten.
Zeile: 0

Wie sehen denn der HTTP Request und die HTTP Response als Text aus?

Alternativ zum Debugger könnte ein loggender HTTP Proxy wie Fiddler2 - https://www.telerik.com/fiddler - eingesetzt werden. (Oder ein Packet Sniffer).

Update:
Zitat:

SOAPRequest ist in Ordnung, SOAPResponse ist leer.
Wenn der Request ok ist, könnte man ihn z.B. via Postman oder Curl senden um dann mehr über die Response zu erfahren. SoapResponse enthält vermutlich nicht die HTTP Headerzeilen.

bcvs 1. Feb 2023 14:38

AW: Soap-Service: XML-Fehler
 
[QUOTE=mjustin;1518161]
Zitat:

Zitat von bcvs (Beitrag 1518141)
Wie sehen denn der HTTP Request und die HTTP Response als Text aus?

Wie komme ich denn daran? In dieser SOAP-Geschichte läuft das ja über den THTTPRIO.

Zitat:

Wenn der Request ok ist, könnte man ihn z.B. via Postman oder Curl senden um dann mehr über die Response zu erfahren. SoapResponse enthält vermutlich nicht die HTTP Headerzeilen.
Genau. SoapResponse ist komplett leer. Das mit Postman probiere ich mal aus.

bcvs 1. Feb 2023 16:18

AW: Soap-Service: XML-Fehler
 
Ich glaube, dieses Postman bringt mich ein Stück weiter:

Wenn ich meinen SOAPRequest damit abschicke, kommt auch dort kein Response an.

Aber: Mein SOAPRequest enthält einen Header zur Authentifizierung:
Code:
     <SOAP-ENV:Header>
          <ns2:Security env:mustUnderstand="true">
               <ns2:UsernameToken ns3:Id="UsernameToken-4C578AF5E8CBB3162A14952041422019">
                    <ns2:Username>******</ns2:Username>
                    <ns2:Password Type="&quot;http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">******</ns2:Password>
                    <ns2:Nonce EncodingType="&quot;http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">SC4DEqvj1tX0yuY6sFXMvQ==</ns2:Nonce>
               </ns2:UsernameToken>
          </ns2:Security>
     </SOAP-ENV:Header>
Wenn ich den weglasse, kommt immerhin ein Response an, der logischerweise besagt, dass man Unauthorized ist:
Code:
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
    <env:Header></env:Header>
    <env:Body>
        <env:Fault>
            <faultcode>env:Server</faultcode>
            <faultstring></faultstring>
            <detail fault:type="Unauthorized" xmlns:fault="http://www.dhl.com/soapfaults"></detail>
        </env:Fault>
    </env:Body>
</env:Envelope>
Also stimmt da anscheinend etwas mit diesem Header nicht.

mjustin 1. Feb 2023 16:32

AW: Soap-Service: XML-Fehler
 
Code:
                    <ns2:Password Type="&quot;http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">******</ns2:Password>
                    <ns2:Nonce EncodingType="&quot;http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">SC4DEqvj1tX0yuY6sFXMvQ==
Was mir auffällt: ... Password Type="&quot; ... und EncodingType="&quot;, also ein kodiertes " am Anfang, aber kein schliessendes " am Ende des Attributs.

Sind diese &quot; am Anfang des Attributs so vorgesehen?

bcvs 1. Feb 2023 16:41

AW: Soap-Service: XML-Fehler
 
Nein, die &quot; waren falsch. Die gehören da nicht hin. Daran liegt es aber nicht. Ohne erhalte ich das selbe Ergebnis.

himitsu 1. Feb 2023 16:49

AW: Soap-Service: XML-Fehler
 
Vielleicht mit Anderen ähnlichen Problemen vergleichen?

https://www.google.com/search?q=%22n...tUnderstand%22

Es gibt da z.B. so Hinweise wie
Zitat:

du scheinst die Soap-Version 1.1 zu verwenden, die geforderten Header sehen aber nach Version 1.2 aus.
uvm.




Einige SOAP-Services kann man auf JSON umstellen
z.B.
Delphi-Quellcode:
accept: application/json,*/*

Dort sieht das Ganze eventuell viel einfacher und nicht so vernamespaced aus.

bcvs 1. Feb 2023 17:05

AW: Soap-Service: XML-Fehler
 
Danke für die Tips.

Ich probiere momentan etwas mit SoapUI https://www.soapui.org/ rum. Da konnte ich funktionierende Beispiele für diese API importieren. Jetzt muss ich nur noch schauen, warum die funktionieren und meins nicht.

mytbo 1. Feb 2023 23:59

AW: Soap-Service: XML-Fehler
 
Auf der DHL Hilfe Seite bekommt man für 18 Sprachen, für einige auch mehrere Lösungswege, den vollständigen Quellcode zum Copy&Pasten. Alle Eingabe- und Ausgabeparameter sind en detail aufgeführt und ausführlich beschrieben.

Wenn ich es mit mORMot umsetze, stehen mir viele Wege offen. Von holzschnittartig bis fein ziseliert. Deine Daten bekommst du am einfachsten mit einem TWinHttp.Get Aufruf. mORMot beherrscht SSL/TLS. Im Folgenden verwende ich Curl, weil der Quellcode aus dem DHL Beispiel kopiert werden kann. Der JSON Response wird mit der Funktion RecordLoadJson in eine Record Struktur deserialisiert. Möglich wäre auch ein Objekt, oder ohne vorherige Definition einer Struktur mit der Funktion InitJson ein DocVariant. Dies ist vorteilhaft, wenn nur einige Werte von Interesse sind. Mit der Funktion GetValueByPath ist ein Direktzugriff möglich. Informationen zu mORMot mit Curl findest du im Forum hier.

Abgebildet ist der Fall Rating GET /rates (Retrieve Rates for a one piece Shipment):
Delphi-Quellcode:
uses
  mormot.core.base,
  mormot.core.data,
  mormot.core.text,
  mormot.core.json,
  mormot.core.buffers,
  mormot.core.variants,
  mormot.core.os,
  mormot.lib.curl,
  mormot.net.client;

type
  TRateRequestResult = packed record
    products: array of record
      productName: RawUtf8;
      productCode: RawUtf8;
      localProductCode: RawUtf8;
      localProductCountryCode: RawUtf8;
      networkTypeCode: RawUtf8;
      isCustomerAgreement: Boolean;
      weight: record
        volumetric: Double;
        provided: Double;
        unitOfMeasurement: RawUtf8;
      end;
      totalPrice: array of record
        currencyType: RawUtf8;
        priceCurrency: RawUtf8;
        price: Currency;
      end;
      totalPriceBreakdown: array of record
        currencyType: RawUtf8;
        priceCurrency: RawUtf8;
        priceBreakdown: array of record
          typeCode: RawUtf8;
          price: Currency;
        end;
      end;
      detailedPriceBreakdown: array of record
        currencyType: RawUtf8;
        priceCurrency: RawUtf8;
        breakdown: array of record
          name: RawUtf8;
          serviceCode: RawUtf8;
          localServiceCode: RawUtf8;
          typeCode: RawUtf8;
          serviceTypeCode: RawUtf8;
          price: Currency;
          isCustomerAgreement: Boolean;
          isMarketedService: Boolean;
          isBillingServiceIndicator: Boolean;
          priceBreakdown: array of record
            priceType: RawUtf8;
            typeCode: RawUtf8;
            price: Currency;
            rate: Double;
            basePrice: Currency;
          end;
        end;
      end;
      pickupCapabilities: record
        nextBusinessDay: Boolean;
        localCutoffDateAndTime: TDateTime;
        GMTCutoffTime: TDateTime;
        pickupEarliest: TDateTime;
        pickupLatest: TDateTime;
        originServiceAreaCode: RawUtf8;
        originFacilityAreaCode: RawUtf8;
        pickupAdditionalDays: Integer;
        pickupDayOfWeek: Integer;
      end;
      deliveryCapabilities: record
        deliveryTypeCode: RawUtf8;
        estimatedDeliveryDateAndTime: TDateTime;
        destinationServiceAreaCode: RawUtf8;
        destinationFacilityAreaCode: RawUtf8;
        deliveryAdditionalDays: Integer;
        deliveryDayOfWeek: Integer;
        totalTransitDays: Integer;
      end;
      items: array of record
        number: Integer;
        breakdown: array of record
          name: RawUtf8;
          serviceCode: RawUtf8;
          localServiceCode: RawUtf8;
          typeCode: RawUtf8;
          serviceTypeCode: RawUtf8;
          price: Currency;
          priceCurrency: RawUtf8;
          isCustomerAgreement: Boolean;
          isMarketedService: Boolean;
          isBillingServiceIndicator: Boolean;
          priceBreakdown: array of record
            priceType: RawUtf8;
            typeCode: RawUtf8;
            price: Currency;
            rate: Double;
            basePrice: Currency;
          end;
        end;
      end;
      pricingDate: TDateTime;
    end;
    exchangeRates: array of record
      currentExchangeRate: Double;
      currency: RawUtf8;
      baseCurrency: RawUtf8;
    end;
    warnings: array of RawUtf8;
  end;

var
  hnd: TCurl;
  res: TCurlResult;
  headers: Pointer;
  responseData: RawByteString;
  responseSize: Int64;
  requestResult: TRateRequestResult;
begin
  if not CurlIsAvailable then Exit; //=>

  hnd := curl.easy_init;
  if hnd <> Nil then
  begin
    curl.easy_setopt(hnd, coSSLVerifyPeer, 0);
    curl.easy_setopt(hnd, coSSLVerifyHost, 0);
    curl.easy_setopt(hnd, coWriteFunction, @CurlWriteRawByteString);
    curl.easy_setopt(hnd, coWriteData, @responseData);

    var docQuery: TDocVariantData;
    docQuery.Init(JSON_FAST_EXTENDED, dvObject);
    docQuery.U['accountNumber'] := '123456789';
    docQuery.U['originCountryCode'] := 'CZ';
    docQuery.U['originCityName'] := 'Prague';
    docQuery.U['destinationCountryCode'] := 'CZ';
    docQuery.U['destinationCityName'] := 'Prague';
    docQuery.I['weight'] := 5;
    docQuery.I['length'] := 15;
    docQuery.I['width'] := 10;
    docQuery.I['height'] := 5;
    docQuery.U['plannedShippingDate'] := '2020-02-26';
    docQuery.B['isCustomsDeclarable'] := False;
    docQuery.U['unitOfMeasurement'] := 'metric';
    curl.easy_setopt(hnd, coURL, Pointer(docQuery.ToUrlEncode('https://api-mock.dhl.com/mydhlapi/rates')));

    var docHeader: TDocVariantData;
    docHeader.Init(JSON_FAST_EXTENDED, dvObject);
    docHeader.U['Message-Reference'] := 'd0e7832e-5c98-11ea-bc55-0242ac13';
    docHeader.U['Message-Reference-Date'] := 'Wed, 21 Oct 2015 07:28:00 GMT';
    docHeader.U['Plugin-Name'] := '';
    docHeader.U['Plugin-Version'] := '';
    docHeader.U['Shipping-System-Platform-Name'] := '';
    docHeader.U['Shipping-System-Platform-Version'] := '';
    docHeader.U['Webstore-Platform-Name'] := '';
    docHeader.U['Webstore-Platform-Version'] := '';

    headers := Nil;
    var headerField: TDocVariantFields;
    headers := curl.slist_append(headers, Pointer(FormatUtf8('accept: %', [JSON_CONTENT_TYPE])));
    headers := curl.slist_append(headers, Pointer(FormatUtf8('Authorization: Basic %', ['ZGVtby1rZXk6ZGVtby1zZWNyZXQ='])));
    for headerField in docHeader.Fields do
    begin
      if headerField.Value <> Nil then
        headers := curl.slist_append(headers, Pointer(FormatUtf8('%: %', [headerField.Name^, headerField.Value^])));
    end;
    curl.easy_setopt(hnd, coHttpHeader, headers);

    curl.easy_setopt(hnd, coCustomRequest, RawUtf8('GET'));
    res := curl.easy_perform(hnd);
    if res = crOk then
    begin
      FileFromString(responseData, MakePath([Executable.ProgramFilePath, 'ResponseData.json']));

      curl.easy_getinfo(hnd, ciSizeDownloadT, responseSize);
      ShowMessage(Format('Download completed: %s', [KB(responseSize)]));

      if RecordLoadJson(requestResult, responseData, TypeInfo(TRateRequestResult)) then
        ShowMessage(Utf8ToString(RawUtf8ArrayToCsv(requestResult.warnings, sLineBreak)));
    end
    else
      ShowMessage(Format('Curl told us %d (%s)', [Ord(res), curl.easy_strerror(res)]));

    curl.slist_free_all(headers);
    curl.easy_cleanup(hnd);
  end;
end;
Disclaimer: ohne Gewähr

Auch für Query und Header Teil musst du keinen DocVariant verwenden, sondern kannst es als Record oder Objekt abbilden und mit UrlEncode/ToCSV Funktionen aufbereiten.

Bis bald...
Thomas

mjustin 2. Feb 2023 07:31

AW: Soap-Service: XML-Fehler
 
Zitat:

Zitat von mytbo (Beitrag 1518194)
Auf der DHL Hilfe Seite bekommt man für 18 Sprachen, für einige auch mehrere Lösungswege, den vollständigen Quellcode zum Copy&Pasten. Alle Eingabe- und Ausgabeparameter sind en detail aufgeführt und ausführlich beschrieben.

Die API Dokumentation scheint - wenn ich nichts übersehen habe - nur die neue REST API zu beschreiben. Die SOAP API hat einen Vorteil durch die Möglichkeit, den gesamten Code für die Datenstrukturen durch den WSDL Importer generieren zu lassen. Das kann von Vorteil sein, wenn man die Ressourcen für das manuelle Erstellen des neuen Codes für die REST API nicht hat. Je mehr der DHL Services man nutzen möchte, desto mehr Zeit lässt sich durch die Codegenerierung einsparen.

Union 2. Feb 2023 08:30

AW: Soap-Service: XML-Fehler
 
Falls eine OpenAPI Doc vorliegt für den REST Service, kannst Du auch diese Objekte automatisch generieren.

mjustin 2. Feb 2023 09:36

AW: Soap-Service: XML-Fehler
 
Zitat:

Zitat von Union (Beitrag 1518204)
Falls eine OpenAPI Doc vorliegt für den REST Service, kannst Du auch diese Objekte automatisch generieren.

Guter Hinweis! Für Delphi gibt es anscheinend diesen Open Source Generator: https://github.com/Zwixx/delphi-openapi-generator

(Gefunden in https://www.delphipraxis.net/1464200-post14.html)

Union 2. Feb 2023 10:24

AW: Soap-Service: XML-Fehler
 
Der genannte Generator erzeugt OpenApi docs.Gesucht ist ja der umgekehrte Weg, aus einer OpenAPI Definition Delphi Code zu erstellen.

Sorry, hatte den verwechselt, es sollte gehen.

bcvs 2. Feb 2023 15:27

AW: Soap-Service: XML-Fehler
 
So, jetzt funktioniert es.

Der Fehler war, dass im XML ein Namespace nicht definiert war.

Dass ist passiert, weil ich den
<SOAP-ENV:Header>

über einen TSoapHeader selbst ins XML eingefügt habe.

Ansonsten erzeugt SOAP das XML ja automatisch.

Daraufhin der der Server dann überhaupt nichts mehr zurückgegeben.


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

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