7 - Fertig machen zur Landung / preparing for landing
Soeben haben wir den .ReadyState 4 erhalten. Dies bedeutet, dass wir eine Serverantwort in Empfang nehmen können. Das hört sich doch eigentlich ganz gut an. Dummerweise bleibt uns an dieser Stelle ein eventueller Blick in die Dokumentation des Webservice nicht erspart.Das oXmlHttp Objekt stellt uns nämlich zwei Eigenschaften zur Verfügung mit deren Hilfe wir die Serverantwort abfragen können.
- .ResponseText
- .ResponseBody
Welche der beiden herangezogen werden muss, darüber informiert uns im Normalfall die Eigenschaft .ResponseType. Aaaaber, es kommt auch schon mal vor, dass dieser auf dem Server (content/type) falsch hinterlegt ist. Das wiederum bedeutet: die hoffentlich vorhandene Dokumentation wälzen und hoffen, dort fündig zu werden.
Grundsätzlich bedeutet ein leerer Eintrag oder "text" innerhalb von .ResponseType dass die Eigenschaft .ResponseText gelesen werden muss. In allen anderen Fällen ist es .ResponseBody. Erschwerend kommt hierbei hinzu, dass nicht jeder Browser jeden .ResponseType unterstützt. Was wohl zumindest in Teilen erklärt, warum manche Webservices bei der Typdeklaration ein wenig schludern.
Wenn es hart auf hart kommt hilft uns jedoch der Debugger. Dort können wir die Inhalte ohne weiteres einsehen (im Code einfach .ResponseText und .ResponseBody jeweils einer Variablen zuweisen und diese im Debugger anzeigen lassen oder direkt in eine Datei ausgeben).
Haben wir uns zuvor eine Tabelle angelegt in welcher wir die diversen Bauteile der Service URI hinterlegt haben, dann können wir an dieser Stelle auch gleich eine weitere Spalte für den .ResponseType anlegen und eintragen ob .ResponseText oder .ResponseBody ausgelesen werden muss.
Der folgende Link gibt eine kurze Übersicht der möglichen .ResponeType Werte:
https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/responseType
Als goldene Regel gilt, dass Binärdaten, Arrays, Dateien (pdf, jpg, xml, json) IMMER im .ResponseBody liegen. Übrigens können sowohl XML als auch JSON neben Datensätzen/Arrays auch Binärdaten transportieren/aufnehmen. Im Normalfall werden Binärdaten für JSON zuvor über BASE64 in Text umgewandelt. Wenn Euch das nicht geläufig sein sollte, dann schaut Euch die Funktion STRCONV(
WITH Thisform.cmgAnhang
FOR liLoop = 1 TO .ButtonCount
lcStream = FILETOSTR( .Buttons( liLoop ).Tag )
lcAttach = lcAttach + [{ "id" : ] + TRANSFORM( liLoop ) + [ , "name" : "] + JUSTFNAME( .Buttons( liLoop ).Tag ) + [" , "file" : "] + STRCONV( lcStream , 13 ) + [" }]
lcAttach = lcAttach + IIF( liLoop < .ButtonCount , [ ,] , [] )
ENDFOR
ENDWITH
TEXT TO lcDlePar TEXTMERGE NOSHOW PRETEXT 1+2+3+8
{
"to" : "<<ALLTRIM( Thisform._To )>>" ,
"from" : "<<ALLTRIM( Thisform._From )>>" ,
"cc" : "<<ALLTRIM( Thisform._Cc )>>" ,
"bcc" : "<<ALLTRIM( Thisform._Bcc )>>" ,
"subject" : "<<ALLTRIM( Thisform._Subject )>>" ,
"body" : "<<Thisform.Converter( Thisform._Body )>>" ,
"attachments" : [ <<lcAttach>> ]
}
ENDTEXT
Aber nun zurück zum eigentlichen Thema um dort weiter zu machen wo das vorangegangene Kapitel endete.
Der folgende Code beinhaltet eine explizite Prüfung auf .ResponseType und versucht im ersten Schritt den .ResponseText auszulesen. Eine Abfrage basierend auf einer Tabellenfeldvorgabe ist sicherlich sinnvoll.
*// something went wrong
IF oXmlHttp.status != 200
=GiveFeedback( oXmlHttp.status )
*// otherwise start computing data
ELSE
LOCAL lcRetVal AS String
STORE [] TO lcRetVal
IF EMPTY( oXmlHttp.ResponseType ) OR LOWER( ALLTRIM( oXmlHttp.ResponseType ) == [text]
TRY
*// try to read .ResponseText
=myFormObj.StatusOutput( [computing ResponseText] )
IF NOT ISNULL( oXmlHttp.responseText )
lcRetVal = oXMLHttp.responseText
ENDIF
CATCH
lcRetVal = []
ENDTRY
ENDIF
IF EMPTY( lcRetVal )
TRY
*// If lcRetVal is empty, read .ResponseBody
=myFormObj.StatusOutput( [computing ResponseBody] )
IF NOT ISNULL( oXmlHttp.responseBody )
lcRetVal = oXMLHttp.responseBody
ENDIF
CATCH
lcRetVal = []
ENDTRY
ENDIF
ENDIF
Ab jetzt steht in der Variablen lcRetVal das Ergebnis aus unserer Webservice Anfrage. Der Inhalt kann alles Mögliche sein.
- Eine PDF-Datei die wir direkt mit STRTOFILE() auf die Platte schreiben können:
lcFile = ADDBS( GETENV( [TEMP] ) ) ;
+ FORCEEXT( DTOC(DATE(),1)+[_]+SYS(3),[pdf])
STRTOFILE( lcRetVal , lcFile , 0 )
- Ein JSON String der in VFP kompatible Daten zerlegt werden muss:
lcSplit = CHR( 13 )
lcStream = Thisform.Tag
lcStream = STRTRAN( lcStream , [\u0026] , [&] )
lcStream = STRTRAN( lcStream , [\u003C] , [<] )
lcStream = STRTRAN( lcStream , [\u003c] , [<] )
lcStream = STRTRAN( lcStream , [\u003E] , [>] )
lcStream = STRTRAN( lcStream , [\u003e] , [>] )
lcStream = STRTRAN( lcStream , [\u00E4] , [ä] )
lcStream = STRTRAN( lcStream , [\u00e4] , [ä] )
lcStream = STRTRAN( lcStream , [\u00C4] , [Ä] )
lcStream = STRTRAN( lcStream , [\u00c4] , [Ä] )
lcStream = STRTRAN( lcStream , [\u00F6] , [ö] )
lcStream = STRTRAN( lcStream , [\u00f6] , [ö] )
lcStream = STRTRAN( lcStream , [\u00D6] , [Ö] )
lcStream = STRTRAN( lcStream , [\u00d6] , [Ö] )
lcStream = STRTRAN( lcStream , [\u00FC] , [ü] )
lcStream = STRTRAN( lcStream , [\u00fc] , [ü] )
lcStream = STRTRAN( lcStream , [\u00DC] , [Ü] )
lcStream = STRTRAN( lcStream , [\u00dc] , [Ü] )
lcStream = STRTRAN( lcStream , [\u00DF] , [ß] )
lcStream = STRTRAN( lcStream , [\u00df] , [ß] )
lcStream = STRTRAN( lcStream , [\u00AC] , [€] )
lcStream = STRTRAN( lcStream , [\u00AC] , [€] )
lcStream = STRTRAN( lcStream , [\u0024] , [$] )
lcStream = STRTRAN( lcStream , [\u00A3] , [£] )
lcStream = STRTRAN( lcStream , [\u00a3] , [£] )
IF LEFT( lcStream , 1 ) = '[' && multiple records
lcStream = STREXTRACT( lcStream, '[' , ']' )
llMultiRec = .T.
ENDIF
Thisform.edit2.Value = []
liRecord = 1
DO WHILE .T.
lcRecord = STRTRAN( STREXTRACT( lcStream , [{] , [}] , liRecord) , [","] , ["] + lcSplit + ["] )
IF NOT EMPTY( lcRecord )
FOR liLoop = 1 TO GETWORDCOUNT( lcRecord , lcSplit )
lcWord = GETWORDNUM( lcRecord , liLoop , lcSplit )
lcColumn = STREXTRACT( lcWord , ["] , ["] , 1 )
lcValue = STREXTRACT( lcWord , ["] , ["] , 3 )
Thisform.edit2.Value = Thisform.edit2.Value + CHR(13) + lcColumn + [ / ] + lcValue
ENDFOR
liRecord = liRecord + 1
ELSE
EXIT
ENDIF
ENDDO
Der o.a. Codeblock stammt 1:1 aus der Demoform die am Ende des Blogeintrags über einen Download-Link abgerufen werden kann.
- Ein Bilddatei (die wie eine PDF direkt als Datei gespeichert wird).
- Ein einfaches "OK" das uns mitteilt, dass der Webservice nun weitere Anfragen für uns bearbeiten wird.
- Eine SessionID mit der wir unsere nächsten Anfragen an den Webservice versehen müssen, damit dieser uns wiedererkennt und gecachte Daten nutzt.
Die Möglichkeiten sind vielfältig. Aber nun sollte es kein unüberwindbares Problem mehr sein, eine passende Reaktion zu programmieren.
Bisher konnte ich mit den in diesem und den vorangegangenen Kapiteln beschriebenen Vorgehensweisen fast alle Webservices ansprechen und deren Daten in VFP weiterverarbeiten. Das bedeutet natürlich nicht, dass ich alle Fallstricke berücksichtigt habe. Manche sind mir noch nicht über den Weg gelaufen oder ich habe sie im Laufe der Zeit schlicht und einfach verdrängt, schließlich liegt mein erster Kontakt mit dem oXmlHttp Objekt schon beinahe 10 Jahre zurück. Sollte also jemandem auffallen das etwas Wichtiges fehlt, dann kommentiert dies bitte. Ich ergänze diese Artikelserie gerne um weitere Informationen.
Zum Abschluß gibt es nun noch einen kleinen Goody :)
Im Rahmen einer Anbindung an ein System das dutzende von Web Services bereitstellt, habe ich vor Jahren einen Prototypen marke 'Quick and Dirty' gebastelt.
Die Form beinhaltet verschiedene ReST basierende Web Service Zugriffe die durchweg als praxistauglich angesehen werden können. Sie erfolgen zwar mit einem Testschlüssel (API Key) und somit werden keine Echtdaten verarbeitet bzw. gesendet, aber es findet eine tatsächliche Kommunikation zu den Diensten des Anbieters statt. Also viel Spaß beim ausprobieren ;)
Hier der Download Link zur Testform:
Die drei oberen (rot umrandeten) Buttons fragen Dienste ab, die JSON Daten liefern. Diese können über den unteren (ebenfalls rot markierten) Button konvertiert werden.
Die drei darunterliegenden Buttons liefern bzw. verarbeiten Daten in anderen Formaten.
Die ZIP enthält zwei Dateien:
- webservicetestform.sct
- webservicetestform.scx
Achja, noch ein kleiner Tipp zum Abschluß:
Da wir für den Zugriff auf Web Services gültige URIs zusammenbauen, können wir diese auch DIREKT im Browser unserer Wahl eingeben und testen. Auf diese Weise sparen wir uns speziell zu Beginn einige mühselige Debugging Sitzungen...
Links zu den restlichen Kapiteln:
Einführung / Introduction
Teil 1: Abkürzungen und was sie bedeuten/ Abbreviations and their meaning
Teil 2: Wer ReST sagt, sagt auch JSON
/ In for a ReST, in for a JSON
Teil 3: Leerzeichen und andere Entitäten
/ BLANKS and other entities
Teil 4: JSON und der goldene Konverter
/ JSON and the golden converter
Teil 5: Die Startvorbereitungen
/ preparations for launch
Teil 6: Kleine Denkpause gefällig?
/ in need of a reflection period?
Teil 7: Fertig machen zur Landung
/ preparing for landing