XPath / XPath-Funktionen / XPath: Sequenz-Funktionen / RESTful Webservice

RESTful Webservice

RESTful Webservice

➪ Die REpresentational-State-Transfer-Architektur ist weder ein Standard noch ein Produkt, sondern ein Konzept zur Umsetzung von Webservices. REST stellt eine Alternative zu SOAP oder XML-RPC dar. Einfach ausgedrückt, bietet ein REST-Webservice die Möglichkeit, via HTTP mit GET, POST, PUT und DELETE einen Request an einen Webserver abzusenden und dabei Informationen zu übertragen. Der Webserver antwortet mit einer Response, die der Client verarbeiten kann.

Auf dieser Seite:

pic/REST1.jpg

HTTP

Das Hypertext Transfer Protocol HTTP (aktuell: HTTP/2) ist ein zustandsloses Dateiübertragungsprotokoll, das häufig zur Übertragung von Hypertext (Webseiten im Internet) auf Browser verwendet wird, aber auch für Dateien anderer Formate (z.B. XML, JSON, CSV, ...) verwendet werden kann.

Beteiligte in diesem Datenaustausch sind der Client und der Server, die jeweils unterschiedliche Nachrichtenarten versenden. Der Client stellt eine Anfrage (Request) an den Server, dieser antwortet mit einer Response. Jede Nachricht hat dabei einen Header (Message Header, HTTP-Header) und einen Body.

DELETE löscht eine Ressource auf dem Server.
GET fordert Informationen vom Server an, z.B. eine Datei. Hierbei können Argumente übergeben werden; die Zeichenlänge des URI soll aber 255 Bytes nicht überschreiten.
HEAD Wie GET; aber es werden nur die Header-Informationen ohne Body angefordert.
OPTIONS Analog zu GET; es werden nur die serverseitig unterstützten Methoden und Merkmale angefordert.
POST erzeugt eine neue Ressource in einer Collection auf dem Server. Beispiel: POST /werte. Wird auch für Updates verwendet.
PUT ersetzt eine Ressource auf dem Server bzw. erzeugt sie, falls sie noch nicht vorhanden ist. Ändert eine Ressource in der Collection via ID, z.B. PUT /werte/1234. Zur klaren Unterscheidung zwischen POST und PUT gibt es lange Debatten.

Welche dieser Anfragen an den Server gerichtet wurden, können Sie beispielsweise auf einem PHP-Webserver so herausfinden:


$anfragemethode = $_SERVER['REQUEST_METHOD'];

... und so behandeln:


switch ($anfragemethode) {
  case 'DELETE':
    // z.B. DELETE-Statement an eine Datenbank 
  case 'GET':
    // z.B. SELECT-Abfrage an eine Datenbank
  case 'POST':
    // z.B. INSERT-Statement an eine Datenbank
  case 'PUT':
    // z.B. UPDATE-Statement an eine Datenbank
    break;
}

Stellen Sie sich nun einen Request vor, der von C#.NET aus an einen Webserver gesendet wird. Wie beschrieben, generiert der Server eine Response, die Sie detailliert auswerten können.


using System.Net;
using System.Collections;
using System.IO;
int zahl = 234;
string url = String.Format("
            http://localhost/wg/wghkm2.php?
            z={0}&format=CSV", zahl);
HttpWebRequest webrequest = 
              (HttpWebRequest) WebRequest.Create(url);
//Falls benoetigt:
//webrequest.Credentials  = new 
//         NetworkCredential("username", "password");
WebResponse response      = webrequest.GetResponse();
IEnumerator eee = (IEnumerator) 
            response.Headers.GetEnumerator();
while (eee.MoveNext())
{
    Ausgeben( eee.Current, 
              response.Headers[eee.Current.ToString()]);
}
eee = null;
Ausgeben("AbsoluteUri", 
          response.ResponseUri.AbsoluteUri);
Ausgeben("AbsolutePath", 
          response.ResponseUri.AbsolutePath);
Ausgeben("Authority", 
          response.ResponseUri.Authority);
Ausgeben("DnsSafeHost", 
          response.ResponseUri.DnsSafeHost);
Ausgeben("Fragment", 
          response.ResponseUri.Fragment);
Ausgeben("Host", 
          response.ResponseUri.Host);
Ausgeben("HostNameType", 
          response.ResponseUri.HostNameType.ToString());
Ausgeben("IdnHost", 
          response.ResponseUri.IdnHost);
Ausgeben("LocalPath", 
          response.ResponseUri.LocalPath);
Ausgeben("OriginalString", 
          response.ResponseUri.OriginalString);
Ausgeben("PathAndQuery", 
          response.ResponseUri.PathAndQuery);
Ausgeben("Port", 
          response.ResponseUri.Port);
Ausgeben("Query", 
          response.ResponseUri.Query);
Ausgeben("Scheme", 
          response.ResponseUri.Scheme);
Ausgeben("UserInfo", 
          response.ResponseUri.UserInfo);
Console.WriteLine();
StreamReader reader;
reader        = new StreamReader(
                response.GetResponseStream());
Ausgeben(reader.ReadToEnd());
reader.Close();
response.Close();

Notwendig für die erfolgreiche Ausgabe ist, dass die Methode Ausgeben entsprechend implementiert ist, beispielsweise so:


static void Ausgeben(object s1, object s2)
{
  Console.WriteLine("{0,15} : {1}", s1, s2);
}
static void Ausgeben(object s1)
{
  Console.WriteLine("{0}", s1);
}

Das Ergebnis dieser Response-Auswertung können Sie hier bewundern:


    Keep-Alive : timeout=5, max=100
    Connection : Keep-Alive
Content-Length : 50
  Content-Type : application/text
          Date : Tue, 19 Dec 2017 22:54:35 GMT
        Server : Apache/2.4.29 (Win32) OpenSSL/1.0.2n PHP/7.1.12
  X-Powered-By : PHP/7.1.12
   AbsoluteUri : http://localhost/wg/wghkm2.php?z=234&format=CSV
  AbsolutePath : /wg/wghkm2.php
     Authority : localhost
   DnsSafeHost : localhost
      Fragment :
          Host : localhost
  HostNameType : Dns
       IdnHost : localhost
     LocalPath : /wg/wghkm2.php
OriginalString : http://localhost/wg/wghkm2.php?z=234&format=CSV
  PathAndQuery : /wg/wghkm2.php?z=234&format=CSV
          Port : 80
         Query : ?z=234&format=CSV
        Scheme : http
      UserInfo :
Hunde;Katzen;Maeuse
3;175;56
6;116;112
9;57;168

Hunde, Katzen, Mäuse

Der Webserver ist also so freundlich, auf einen Request mit einem Response zu antworten, der vielfältige Formate annehmen kann, beispielsweise XML, JSON oder CSV. Nun muss der Client in die Lage versetzt werden, diesen Response zu verarbeiten.

Das folgende Beispiel richtet sich via GET an eine bestimmte URL, dabei werden zwei Parameter "z" und "format" übergeben.


http://localhost/wg/wghkm2.php?z=234&format=XML

Hinter dieser Webadresse habe ich mit PHP einen kleinen Webservice eingerichtet. Dieser löst eine Aufgabe, die ich gerne in meinen Programmierseminaren durchführe. Suchen Sie sich eine Zahl zwischen 100 und 10000. Beispiel: 234.

Nehmen Sie nun an, Sie haben 234 Euro und möchten für genau diese Summe 234 Tiere kaufen. Ein Hund kostet 15 Euro, eine Katze 1 Euro, eine Maus 25 Cent. Von jeder Tierart soll mindestens ein Exemplar gekauft werden. Wie viele Hunde, Katzen und Mäuse kaufen Sie? Bitte listen Sie alle Möglichkeiten auf.

Das PHP-Script, das ich wghkm2.php genannt und im Verzeichnis des Apache-Webservers unter htdocs/wg/wghkm2.php abgelegt habe, hat diesen Aufbau:


<?php
function hkm_concat($zahl, $resptyp){
 $hunde = 3;
 $maus  = 56;
 $katze = $zahl - $hunde - $maus;
 $resp = "";
 while($katze > 0){
  if ($resptyp == "XML"){
   $resp = $resp . "<ds>";
   $resp = $resp . "<hunde>$hunde</hunde>";
   $resp = $resp . "<katzen>$katze</katzen>";
   $resp = $resp . "<maeuse>$maus</maeuse>";
   $resp = $resp . "</ds>";
  }
  else if ($resptyp == "JSON"){
   $resp = $resp . "{\"hunde\":$hunde, ";
   $resp = $resp . "\"katzen\":$katze, ";
   $resp = $resp . "\"maeuse\":$maus}";
  }
  else if ($resptyp == "CSV")
   $resp = $resp . "$hunde;$katze;$maus";
  $hunde = $hunde + 3;
  $maus  = $maus + 56;
  $katze = $zahl - $hunde - $maus;
  if($katze > 1){
   if ($resptyp == "JSON") $resp = $resp . ", ";
   if ($resptyp == "CSV") $resp = $resp . "\r\n";
  }
 }
 return $resp;
}
function responseXML($zahl) {
 header('Content-Type: application/xml');
 $wgresp = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>";
 $wgresp = $wgresp . "<ergebnisse maximalwert=\"10000\" ";
 $wgresp = $wgresp . "eingabewert=\"$zahl\">";
 echo $wgresp . hkm_concat($zahl, "XML") . "</ergebnisse>"; 
}
function responseJSON($zahl) {
 header("Content-Type:application/json");
 $wgresp = "[";
 echo $wgresp . hkm_concat($zahl, "JSON") . "]"; 
}
function responseCSV($zahl) {
 header("Content-Type:application/text");
 $wgresp = "Hunde;Katzen;Maeuse\r\n";
 echo $wgresp . hkm_concat($zahl, "CSV");
}
if(isset($_GET['z'])) {
 $tmpz = $_GET['z'];
 if(is_numeric($tmpz)){
  $tmpz = abs(floor($tmpz));
  if($tmpz > 10000) $tmpz = 10000;
  if(isset($_GET['format']) ){
   if($_GET['format'] == "XML") {
    responseXML($tmpz);
   }
   else if($_GET['format'] == "CSV") {
    responseCSV($tmpz);
   }
   else{
    responseJSON($tmpz);
   }
  }
  else{
   responseXML($tmpz);
  } 
 }
 else echo "parameter z is not numeric: $z";
}
else echo "Invalid Request: missing parameter z";
?>

Einen korrekt laufenden Apache-Webserver vorausgesetzt, sprechen Sie das PHP-Script unter der bereits benannten URI (und den darin übergebenen Parametern) an:


http://localhost/wg/wghkm2.php?z=234&format=XML

Sie erhalten das Ergebnis:


<?xml version="1.0" encoding="ISO-8859-1"?>
<ergebnisse maximalwert="10000" eingabewert="234">
  <ds>
    <hunde>3</hunde>
    <katzen>175</katzen>
    <maeuse>56</maeuse>
  </ds>
  <ds>
    <hunde>6</hunde>
    <katzen>116</katzen>
    <maeuse>112</maeuse>
  </ds>
  <ds>
    <hunde>9</hunde>
    <katzen>57</katzen>
    <maeuse>168</maeuse>
  </ds>
</ergebnisse>

Ändern Sie die Anfrage hinsichtlich der Formatierung, z.B. CSV, ...


http://localhost/wg/wghkm2.php?z=234&format=CSV

... so sieht das Ergebnis so aus:


Hunde;Katzen;Maeuse
3;175;56
6;116;112
9;57;168

Ändern Sie die Anfrage nochmals für JSON-Formatierung, ...


http://localhost/wg/wghkm2.php?z=234&format=JSON

... dann sieht das Ergebnis so aus:


[
  {"hunde":3, "katzen":175, "maeuse":56}, 
  {"hunde":6, "katzen":116, "maeuse":112}, 
  {"hunde":9, "katzen":57,  "maeuse":168}
]

XML-REST mit XSLT 3.0

Möchten Sie den webbasierten XML-Strom mit XSLT auswerten, so ist nichts einfacher als das:


 <xsl:variable name="vinput" 
   select="doc('http://localhost/wg/wghkm2.php?
                z=234&amp;format=XML')"/>
 <xsl:template match="/"> 
  <hundekatzenmaeuse>
   <xsl:for-each select="$vinput/ergebnisse/ds">
    <erg h="{hunde}" k="{katzen}" m="{maeuse}"/>
   </xsl:for-each>
  </hundekatzenmaeuse>
 </xsl:template>

Das Ergebnis lautet dann:


<hundekatzenmaeuse>
 <erg h="3" k="175" m="56"/>
 <erg h="6" k="116" m="112"/>
 <erg h="9" k="57" m="168"/>
</hundekatzenmaeuse>

XML-REST mit C#.NET

Alternativ können Sie den XML-Input auch mit C#.NET auswerten. Hier stehen diverse Ansätze zur Verfügung, verwenden Sie einmal die DOM-Version.

Die Klasse HttpWebRequest startet einen Request an den Server, der ein Objekt der Klasse WebResponse generiert; dieser wiederum wird via StreamReader in einen XML-String ausgelesen.


  using System.Net;
  using System.Xml;
  using System.IO;
  static void Main(string[] args)
  {
   int zahl = 234;
   string url = String.Format(
              "http://localhost/wg/wghkm2.php?
               z={0}&format=XML", zahl);
   HttpWebRequest webrequest = null;
   webrequest = (HttpWebRequest) WebRequest.Create(url);
   XmlDocument dom = new XmlDocument();
   using (var response = webrequest.GetResponse())
   {
    using (var reader = new StreamReader(
                      response.GetResponseStream()))
    {
     dom.LoadXml(reader.ReadToEnd());
     foreach (System.Xml.XmlNode n 
             in dom.SelectNodes("/ergebnisse/ds"))
     {
      int h = Int32.Parse(
              n.SelectSingleNode("hunde").InnerText);
      int k = Int32.Parse(
              n.SelectSingleNode("katzen").InnerText);
      int m = Int32.Parse(
              n.SelectSingleNode("maeuse").InnerText);
      int s = h + k + m;
      Console.WriteLine("{0,9}{1,9}{2,9}{3,15}"
                         , h, k, m, s);
     }
    }
   }
   dom = null;
  }

Das Ergebnis sieht so aus:


    3   175    56      234
    6   116   112      234
    9    57   168      234

JSON-REST mit C#.NET auswerten

Für die Konvertierung des JSON-Strings mit


System.Web.Script.Serialization.JavaScriptSerializer

ist es notwendig, die relevante Klasse bzw. Struktur, deren Objekte via Deserialisierung generiert werden sollen, zu definieren. Statt class können Sie auch mit struct arbeiten.


struct dss
{
    public int hunde, katzen, maeuse;
}

Der Aufruf des WebRequest gibt Ihnen diesen JSON-String zurück:


[
  {"hunde":3, "katzen":175, "maeuse":56}, 
  {"hunde":6, "katzen":116, "maeuse":112}, 
  {"hunde":9, "katzen":57,  "maeuse":168}
]

Wie vorher startet ein Objekt der Klasse HttpWebRequest einen Request an den Server, der ein Objekt der Klasse WebResponse generiert; dieser wiederum wird via StreamReader in einen JSON-String ausgelesen. Die Deserialisierung in ein Objektarray (dss[]) erfolgt über die Klasse JavaScriptSerializer. Nun geht es nur noch darum, dieses Array auszuwerten.


 using System.Net;
 using System.Web.Script.Serialization;
 static void Main(string[] args)
 {
  int zahl = 234;
  string url;
  url = String.Format("http://localhost/wg/wghkm2.php?
                       z={0}&format=JSON", zahl);
  HttpWebRequest webrequest = null;
  webrequest = (System.Net.HttpWebRequest)
               System.Net.WebRequest.Create(url);
  JavaScriptSerializer js = null;
  js = new JavaScriptSerializer();
  using (WebResponse response = webrequest.GetResponse())
  {
  using (var reader = new System.IO.StreamReader(
            response.GetResponseStream()))
  {
   string ss = reader.ReadToEnd();
   dss[] ergebnisse = js.Deserialize<dss[]>(ss);
   foreach (dss d in ergebnisse)
   {
   Console.WriteLine("{0,9}{1,9}{2,9}{3,15}", 
        d.hunde, 
        d.katzen, 
        d.maeuse, 
        (d.hunde + d.katzen + d.maeuse));
   }
  }
  }
  Console.ReadLine();
 }

Auch hier erhalten Sie das bereits gesehene Ergebnis:


    3   175    56      234
    6   116   112      234
    9    57   168      234

JSON-REST mit XSLT 3.0 auswerten

Die Auswertung eines JSON-basierten Webservice ist auch mit XSLT 3.0 möglich:


 <xsl:template match="/"> 
  <hundekatzenmaeuse>
   <xsl:for-each 
    select="json-doc('http://localhost/wg/wghkm2.php?
                              z=234&amp;format=JSON')?*">
    <JS-Objekt anzahlItems="{map:size(.)}">    
     <xsl:for-each select="map:for-each(., 
      function ($k, $v){ concat($k, '|', $v)})">      
      <info name="{substring-before(xs:string(.), '|')}">
       <xsl:value-of select="substring-after(., '|')"/>
      </info>
     </xsl:for-each>
    </JS-Objekt>
   </xsl:for-each>
  </hundekatzenmaeuse>
 </xsl:template>

Das Ergebnis:


<hundekatzenmaeuse>
 <JS-Objekt anzahlItems="3">
  <info name="hunde">3</info>
  <info name="katzen">175</info>
  <info name="maeuse">56</info>
 </JS-Objekt>
 <JS-Objekt anzahlItems="3">
  <info name="hunde">6</info>
  <info name="katzen">116</info>
  <info name="maeuse">112</info>
 </JS-Objekt>
 <JS-Objekt anzahlItems="3">
  <info name="hunde">9</info>
  <info name="katzen">57</info>
  <info name="maeuse">168</info>
 </JS-Objekt>
</hundekatzenmaeuse>

JSON-REST mit XQuery auswerten

Die Auswertung des JSON-basierten Webservice ist selbstverständlich auch mit möglich. Da Sie es im vorliegenden Fall mit einem Array zu tun haben, ist die Einbindung des entsprechenden Namespaces erforderlich, um auf die Spezialfunktionen size und get zugreifen zu können.

Mit der -Funktion wird der JSON-Response aus dem Webservice in die temporäre Variable $vvar geladen; von dort aus geht es mit den Array-Funktionen weiter. $asize übernimmt die Anzahl der Objekte im Array; in Schleifenkonstruktionen über alle Objekte dieses Arrays (der Array-Index ist via $x ansprechbar, jedes Datenfeld darin mit $y) kommen Sie zum Ergebnis.


declare namespace 
    array = "http://www.w3.org/2005/xpath-functions/array";
let $vvar := 
    json-doc('http://localhost/wg/wghkm2.php?
                     z=234&amp;format=JSON')
let $asize := array:size($vvar)
return
<erg anzahlObjekte="{$asize}">
 { 
  for $x in 1 to $asize
    for $y in array:get($vvar, $x) return    
    <Objekt index="{$x}" summe="{sum($y?*)}">
        <hunde>{$y?hunde}</hunde>
        <katzen>{$y?katzen}</katzen>
        <maeuse>{$y?maeuse}</maeuse>
    </Objekt>
 }
</erg>

Und so sieht hier das Ergebnis aus:


<?xml version="1.0" encoding="UTF-8"?>
<erg anzahlObjekte="3">
  <Objekt index="1" summe="234">
   <hunde>3</hunde>
   <katzen>175</katzen>
   <maeuse>56</maeuse>
  </Objekt>
  <Objekt index="2" summe="234">
   <hunde>6</hunde>
   <katzen>116</katzen>
   <maeuse>112</maeuse>
  </Objekt>
  <Objekt index="3" summe="234">
   <hunde>9</hunde>
   <katzen>57</katzen>
   <maeuse>168</maeuse>
  </Objekt>
</erg

wg / 4. April 2018



Fragen? Anmerkungen? Tips?

Bitte nehmen Sie Kontakt zu mir auf.






Vielen Dank für Ihr Interesse an meiner Arbeit.


V.i.S.d.P.: Wilfried Grupe * Klus 6 * 37643 Negenborn

☎ 0151. 750 360 61 * eMail: info10@wilfried-grupe.de

www.wilfried-grupe.de/REST_Webservice.html