Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asynchronous PHP SOAP server send Acknowledgement message before response?

Tags:

php

soap

I am not certain how to do this. The specs for the SOAP service I am writing says it needs to send back an acknowledgement message even before it responds with the requested response to the request.

How is this done in PHP? I have seen no examples of how to do this.

From the requirements doc:

One acknowledgement message is sent by Integration Partner to Vendor for every SubmitInv message request. A single acknowledgement message is also sent by Vendor to the Integration Partner from every RequestInv message respons

This is NOT a standard TCP ack response. It is a custom SOAP formatted response that is their acknowledgement that the request was received. See example below.

After questioning the vendor:

They claim that it is a legacy system and it was written to process in that flow. They cannot, at this time, change it. I told him that in 20+ yrs programming, I have NEVER seen any SOAP system require an ACK. He claimed that it had to do with having to "wait" for the responses. Apparently they don't understand how to properly handle stateless processing.

I have already attempted to do it using the PHP Output Buffering functions as outlined below by FoxVSky, it does not work in a SOAP transaction. Also, the standard SOAP library, the one built-in to PHP, nor the Zend SOAP library have a feature to do this.

Example:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 
               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
               xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <soap:Body>
    <PAddRs>
      <RqUID>f11958c8-3fde-42ca-bd94-94fdfca316ef</RqUID>
      <PKey>46dba062-2105-4851-831f-a1d364741329</PKey>
      <AppStatus>
        <AppStatusCode>Accept</AppStatusCode>
      </AppStatus>
    </PAddRs>
  </soap:Body>
</soap:Envelope>
like image 291
MB34 Avatar asked May 09 '18 14:05

MB34


1 Answers

Ok, I have implemented the acknowledgement messaging in my SOAP service, here is how it is called from the client:

<?php
require_once __DIR__ . '/vendor/autoload.php';
$options = array();
$options['cache_wsdl'] = WSDL_CACHE_NONE;
$options['soap_version'] = SOAP_1_2;
$client = new Zend\Soap\Client("http://localhost/soap/server.php?wsdl", $options);

try {
    // Currently loading example request
    $xml = simplexml_load_file('RequestExample.xml');
    $t_xml = new DOMDocument();
    $t_xml->loadXML($xml->asXML());
    $xml = $t_xml->saveXML($t_xml->documentElement);
    $response = $client->ReqInv($xml);
} catch (Exception $e) {
    $response = 'Exception: '. $e. "\n"; 
}
echo $response;    

And my service:

<?php
require_once __DIR__ . '/vendor/autoload.php';
require(__DIR__ . '/PTResp.php');

use Zend\Soap\AutoDiscover;
use Zend\Soap\Server;
use Zend\Soap\Wsdl;

class PT {
    /**
     * function ReqInv
     * Function to return the inventory for the passed request.
     * 
     *  @param string $request 
     *  @return string
     */
    function ReqInv($request) {
        $pt = new PTResp($request);
        return $pt->toString();
    }   
}

if (isset($_GET['wsdl'])) {
    $wsdl = new AutoDiscover();
    $wsdl->setUri('http://localhost/soap/server.php');
    $wsdl->setClass('PT');
    $wsdl->handle();
} else {
    $server = new Zend\Soap\Server('http://localhost/soap/server.php?wsdl');
    $server->setClass('PT');
    $server->setEncoding('ISO-8859-1');
    $server->handle();
}

And my class (in PTResp.php):

class PT {

    function __construct($xml) {
        $this->m = new Mustache_Engine;
        $this->xml = @simplexml_load_string($xml);
        $this->xml->registerXPathNamespace(<my namespace info>);
        $this->SendAck();
        $this->BuildResponse();
    } // function __construct

    /*
    * This is the function that is actually called to return the response to the client.
    */
    function toString() {
        $domxml = new DOMDocument('1.0');
        $domxml->preserveWhiteSpace = false;
        $domxml->formatOutput = true;
        $domxml->loadXML($this->response);
        $this->response = $domxml->saveXML($domxml->documentElement);
        return $this->response;
    } // function toString    

    function SendAck() {        
        $this->Status = "Accept";
        $xml_post_string = $this->m->render(
        '<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
            <soap:Body>
                <ProcurementAddRs xmlns=MyNamespaceInfo">
                    <RqUID>{{RqUID}}</RqUID>
                    <PKey>{{PKey}}</PKey>
                    <ApplicationStatus>
                        <ApplicationStatusCode>{{Status}}</ApplicationStatusCode>
                    </ApplicationStatus>
                </ProcurementAddRs>
            </soap:Body>
        </soap:Envelope>', array("RqUID" =>$this->RqUID, "PKey"=>$this->PKey, "Status"=>$this->Status));
        $url = 'http://localhost/soap/gotit.php'; // in this test, it writes the response to a file. I will be sending it to the endpoint from here
        $this->curl_post_async($url, $xml_post_string);
    } // function SendAck

    function curl_post_async($url, $post_string){
        $parts=parse_url($url);

        $fp = fsockopen($parts['host'],
            isset($parts['port'])?$parts['port']:80,
            $errno, $errstr, 30);

        $out = "POST ".$parts['path']." HTTP/1.1\r\n";
        $out.= "Host: ".$parts['host']."\r\n";
        $out.= "Content-Type: text/xml\r\n";
        $out.= "Content-Length: ".strlen($post_string)."\r\n";
        $out.= "Connection: Close\r\n\r\n";
        if (isset($post_string)) $out.= $post_string;
        fwrite($fp, $out);
        fclose($fp);
    } // function curl_post_async  

    function BuildResponse() {
        $this-response = "<XML response that is built goes here>";
    }

}
like image 90
MB34 Avatar answered Nov 01 '22 23:11

MB34