SOAP
The intention was to offer a simulation as a web service, the client sends the configuration of the simulation and a set of parameters, both as key-value pairs and the simulation returns key performance indicators (kpi) again as key-value pairs.
Since I found no good example of WSDL + source code to operate on key-value pairs, here is my example.
Once I got this working in PHP, I tried to migrate the stuff to Python, but testing multiple SOAP-packages, I failed to get the server running. So, finally I descided to use a REST interface with JSON data format, which is much easier!
WSDL File
<?xml version="1.0"?> <!-- For validation use: https://www.wsdl-analyzer.com/ --> <wsdl:definitions name="mySimulator" targetNamespace="http://localhost/soap/" xmlns:tns="http://localhost/soap/" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" > <wsdl:documentation> Definition for a web service to execute the simulation. </wsdl:documentation> <!-- specify data elements of SOAP service --> <wsdl:types> <xsd:schema targetNamespace="http://localhost/soap/"> <xsd:element name="id" type="string"/> <xsd:element name="response" type="string" /> <xsd:element name="kvp"> <xsd:complexType> <xsd:sequence> <xsd:element name="key" nillable="true" type="xsd:anyType" /> <xsd:element name="value" nillable="true" type="xsd:anyType" /> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="parameter"> <xsd:complexType> <xsd:sequence minOccurs="1" maxOccurs="unbounded"> <xsd:element name="kvp" /> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="configuration"> <xsd:complexType> <xsd:sequence minOccurs="1" maxOccurs="unbounded"> <xsd:element name="kvp" /> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="result"> <xsd:complexType> <xsd:sequence minOccurs="1" maxOccurs="unbounded"> <xsd:element name="kvp" /> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="modelConfiguration"> <xsd:complexType> <xsd:sequence> <xsd:element name="id" /> <xsd:element name="configuration" /> <xsd:element name="parameter" /> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:element name="simulationResult"> <xsd:complexType> <xsd:sequence> <xsd:element name="id" /> <xsd:element name="result" /> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema> </wsdl:types> <!-- specify messages of SOAP service, these may consist of multiple simple elements, structures holding complex elements are defined as even more complex elements! --> <wsdl:message name="modelConfigurationMsg"> <wsdl:part element="tns:modelConfiguration" name="modelConfiguration" /> </wsdl:message> <wsdl:message name="simulationResultMsg"> <wsdl:part element="tns:simulationResult" name="simulationResult" /> </wsdl:message> <wsdl:message name="pingWsMsg"> <wsdl:part element="tns:id" name="id" /> </wsdl:message> <wsdl:message name="responseMsg"> <wsdl:part element="tns:id" name="id" /> <wsdl:part element="tns:response" name="response" /> </wsdl:message> <!-- specify the ports of the SOAP service, these are the available WS-functions --> <wsdl:portType name="mySimulator"> <wsdl:operation name="pingWS"> <wsdl:input message="tns:pingWsMsg"/> <wsdl:output message="tns:responseMsg"/> </wsdl:operation> <wsdl:operation name="runSimulation"> <wsdl:input message="tns:modelConfigurationMsg"/> <wsdl:output message="tns:simulationResultMsg"/> </wsdl:operation> </wsdl:portType> <!-- specify the binding of the SOAP service, the WS-functions are mapped to URLs --> <wsdl:binding type="tns:mySimulator" name="mySimulatorSOAP"> <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http" /> <wsdl:operation name="pingWS"> <soap:operation soapAction="http://localhost/soap/pingWS" /> <wsdl:input><soap:body use="literal" /></wsdl:input> <wsdl:output><soap:body use="literal" /></wsdl:output> </wsdl:operation> <wsdl:operation name="runSimulation"> <soap:operation soapAction="http://localhost/soap/runSimulation" /> <wsdl:input><soap:body use="literal" /></wsdl:input> <wsdl:output><soap:body use="literal" /></wsdl:output> </wsdl:operation> </wsdl:binding> <!-- finally glue everything to the WebService, the location tells where WSDL and SOAP server are --> <wsdl:service name="mySimulator"> <wsdl:port binding="tns:mySimulatorSOAP" name="mySimulatorSOAP"> <soap:address location="http://localhost/soap/" /> </wsdl:port> </wsdl:service> </wsdl:definitions>
SOAP Server
<?php /* * Example SOAP Server.php source code * to exchange complex data structures like key-value pairs. * Debugging is mainly done by Client.php * Tranfered SOAP structure is parsed and dumped to "foo.html" * 2020-01-10 * Dr. Arne Jachens */ //for persistent session session_start(); /* * WebService functions need to match the name in the WSDL * which provides the WS "mySimulatorSOAP" */ class mySimulatorSOAP { /* ====================================== * Simple WS-function to test the connection, * bases on simple message structures, * $id is intentionally modified. */ public function pingWS($idObj) { #one single argument is not nested in array() $id = $idObj+1; $result = "Hello"; #array ordering needs to match the WSDL message! $pingRes = array( "id"=>$id, "response"=>$result, "foo"=>"bar" #not in wsdl, transmitted, but ignored by client ); return $pingRes; } /* ====================================== * First the model is configured for the specific user, * according to the $configuration and the $parameter. * Then the simulation is executed and key-performance-indicators * are returned to the caller. */ public function runSimulation($modelConfiguration) { $kvpSets = array("configuration","parameter"); list($html,$id,$foundKVPsets) = parseSOAPobject($modelConfiguration,$kvpSets); $configuration = $foundKVPsets["configuration"]; $parameter = $foundKVPsets["parameter"]; $fid = fopen("foo.html","w"); fputs($fid,"<html>".$html."</html>"); fclose($fid); $id = $id+1; #show that we are able to modify $keysC = array_keys($configuration); $keysCStr = implode(",",$keysC); $result = array("kpi1" => $keysCStr,"kpi2" => "3.14159"); $simulationResult = array( "id"=>$id, "result"=>$result ); return $simulationResult; } } /* ====================================== * Our complex SOAPobjects have the $id as first argument, * followed by one or two key-value-pairs */ function parseSOAPobject($SOAPobj,$kvpSets){ $html = ""; $SOAPobjArr = get_object_vars($SOAPobj); $id = $SOAPobjArr["id"]; for($k=0;$k<count($kvpSets);$k++){ $setsFound[$kvpSets[$k]] = array(); $SOAPsubObj = $SOAPobjArr[$kvpSets[$k]]; $item = get_object_vars($SOAPsubObj)["item"]; $i=0; $tmpArr = array(); foreach ($item as $thisObj) { $i++; $thisItem = get_object_vars($thisObj); $tmpArr[$thisItem["key"]] = $thisItem["value"]; } #item $setsFound[$kvpSets[$k]] = $tmpArr; } #kvps $html.= "<p><aj>parseSOAPobject:</aj></br>ID= <b>".$id."</b></p>n"; for($k=0;$k<count($kvpSets);$k++){ $html.= "<p>".$kvpSets[$k]."</p>n"; $thisKVP = $setsFound[$kvpSets[$k]]; $html.= print_r($thisKVP,true); } return array($html,$id,$setsFound); } /* ====================================== * SOAP Webservice server definition, * the handle() executes the local function with the WS-function name */ $wsdl = "http://localhost/soap/mySimulator/mySimulator.wsdl"; $uri = "http://localhost/soap/mySimulator"; $server = new SOAPServer($wsdl, array( // WSDL defines web service "uri" => $uri, // namespace "encoding" => "UTF-8", // character encoding "soap_version" => SOAP_1_2 // SOAP version )); $server->setClass("mySimulatorSOAP"); //WS binding name in WSDL $server->setPersistence(SOAP_PERSISTENCE_SESSION); $server->handle(); ?>
SOAP Client
<?php /* * Example SOAP Client.php source code * to exchange complex data structures like key-value pairs. * Debug and test output are formatted as HTML page -> open in browser. * 2020-01-10 * Dr. Arne Jachens */ /* ====================================== * Construct minimal HTML page */ function HeadFoot(){ $head = "<html> <head> <link rel="STYLESHEET" href="http://www.arne.jachens.de/CMSlayout.css" /> <link REL="SHORTCUT ICON" HREF="http://www.jachens.de/Grafik/favicon.ico"> </head>n"; $foot = "</html>"; return array($head,$foot); } /* ====================================== * Initialize the SOAP connection, * the WSDL is interpreted and the available features shown. */ function initSOAP(){ $wsdl = "http://localhost/soap/mySimulator.wsdl"; $options = array( "soap_version" => SOAP_1_2, "features" => SOAP_SINGLE_ELEMENT_ARRAYS, "location" => "http://localhost/soap/server.php", "trace" => true, "connection_timeout" => 5 //seconds ); $soap = new SoapClient($wsdl, $options); /* understand, what is going on: */ $html = "<h1>Initialize SOAP 4 WSDL</h1>n"; $html.= "<p><a href="https://www.wsdl-analyzer.com/">wsdl-analyzer.com</a></p>n"; $functions = $soap->__getFunctions(); $html.= "<ul>"; for($f=0;$f<count($functions);$f++){ $html.= "<li>".$functions[$f]."</li>"; } #f $html.= "</ul>n"; $types = $soap->__getTypes(); $html.= "<ul>"; for($f=0;$f<count($types);$f++){ $html.= "<li>".$types[$f]."</li>"; } #f $html.= "</ul>n"; return array($soap,$html); } /* ====================================== * Show XML that is exchanged via SOAP */ function debugSOAP($soap,$result){ $html=""; if (is_soap_fault($result)) { trigger_error($html="SOAP-Fehler: (Fehlernummer: {$result->faultcode}, " ."Fehlermeldung: {$result->faultstring})", E_USER_ERROR); } $lastRequ = htmlentities($soap->__getLastRequest()); $lastRequ = str_replace("><","><br><",$lastRequ); $lastResp = htmlentities($soap->__getLastResponse()); $lastResp = str_replace("><","><br><",$lastResp); $html.= "<p>Header:n<tt>" . htmlentities($soap->__getLastRequestHeaders()) . "</tt></p>n"; $html.= "<p>Request:n<tt>" .$lastRequ. "</tt></p>n"; $html.= "<p>Response:n<tt>" .$lastResp. "</tt></p>n"; $html.= "<p>var_export of result: <pre>"; $html.= var_export($result,true); $html.= "</pre>n"; return $html; } /* ====================================== * Simply ping the WebService, * to test connection with simple messages, * result comes with two simple elements. */ function pingWS($soap,$id){ $html = "<h1>pingWS</h1>"; try{ #single, simple argument is given without array() $responseMsg = $soap->pingWS($id); } catch (Exception $e) { $html.= "<h2>Exception Error!</h2>"; $html.= $e->getMessage(); } $html.= debugSOAP($soap,$responseMsg); $retId = $responseMsg["id"]; $response = $responseMsg["response"]; $html.="<p>rid= ".$retId."</p>"; $html.="<p>response= ".$response."</p>"; return $html; } /* ====================================== * Configure model according to user specific settings, * returns 1 if everything is okay. */ function runSimulation($soap,$id,$configuration,$parameter){ $html = "<h1>Run Simulation</h1>"; $modelConfiguration = array( "id" => $id, "configuration" => $configuration, "parameter" => $parameter, ); $html.= "<p>Argument of function call:</p>"; $html.= "<pre>".var_export($modelConfiguration,true)."</pre>"; try{ $simulationResult = $soap->runSimulation($modelConfiguration); } catch (Exception $e) { $html.= "<h2>Exception Error!</h2>"; $html.= $e->getMessage(); } $html.= debugSOAP($soap,$simulationResult); $kvpSets = array("result"); list($tmp,$id,$result) = parseSOAPobject($simulationResult,$kvpSets); $html.= $tmp; return $html; } /* ====================================== * Our complex SOAPobjects have the $id as first argument, * followed by one or two key-value-pairs */ function parseSOAPobject($SOAPobj,$kvpSets){ $html = ""; $SOAPobjArr = get_object_vars($SOAPobj); $id = $SOAPobjArr["id"]; for($k=0;$k<count($kvpSets);$k++){ $setsFound[$kvpSets[$k]] = array(); $SOAPsubObj = $SOAPobjArr[$kvpSets[$k]]; $item = get_object_vars($SOAPsubObj)["item"]; $i=0; $tmpArr = array(); foreach ($item as $thisObj) { $i++; $thisItem = get_object_vars($thisObj); $tmpArr[$thisItem["key"]] = $thisItem["value"]; } #item $setsFound[$kvpSets[$k]] = $tmpArr; } #kvps $html.= "<p><aj>parseSOAPobject:</aj></br>ID= <b>".$id."</b></p>n"; for($k=0;$k<count($kvpSets);$k++){ $html.= "<p>".$kvpSets[$k]."</p>n"; $thisKVP = $setsFound[$kvpSets[$k]]; $html.= print_r($thisKVP,true); } return array($html,$id,$setsFound); } /* ====================================== * Compose HTML page and execute available WS-functions */ list($head,$foot) = HeadFoot(); echo $head; list($soap,$html) = initSOAP(); echo $html; $id="12345"; $html = pingWS($soap,$id); echo $html; $id="12345"; $configuration = array("color" => "green", "engine" => "electric"); $parameter = array("a" => "1", "b" => "2"); $html = runSimulation($soap,$id,$configuration,$parameter); echo $html; echo $foot; ?>