Асинхронный сервер PHP SOAP отправляет сообщение подтверждения перед ответом?

Я не уверен, как это сделать. Спецификации для службы SOAP, которую я пишу, говорят, что необходимо отправить обратно сообщение подтверждения даже до того, как оно ответит запрошенным ответом на запрос.

Как это делается в PHP? Я не видел примеров того, как это сделать.

Из требований doc:

Одно сообщение подтверждения отправляется партнером по интеграции поставщику для каждого запроса на запрос SubmitInv. Одно сообщение подтверждения также отправляется Поставщиком партнеру по интеграции из каждой реакции сообщения RequestInv

Это НЕ стандартный ответ TCP ack. Это настраиваемый отформатированный ответ SOAP, который является подтверждением того, что запрос был получен. См. Пример ниже.

После опроса поставщика:

Они утверждают, что это устаревшая система, и она была написана для обработки в этом потоке. Они не могут в это время изменить его. Я сказал ему, что в программировании 20+ yrs я НИКОГДА не видел, чтобы какая-либо система SOAP требовала ACK. Он утверждал, что это связано с необходимостью "ждать" ответов. По-видимому, они не понимают, как правильно обрабатывать обработку без гражданства.

Я уже пытался сделать это с помощью функций буферизации вывода PHP, как описано ниже FoxVSky, он не работает в транзакции SOAP. Кроме того, стандартная библиотека SOAP, одна встроенная в PHP, а также Zend SOAP-библиотека имеют функцию для этого.

Пример:

<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>

Ответы

Ответ 1

Хорошо, я выполнил обмен сообщениями подтверждения в моей службе SOAP, вот как он вызывается от клиента:

<?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;    

И мое служение:

<?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();
}

И мой класс (в 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>";
    }

}

Ответ 2

Вы можете использовать функции управления выходом PHP

Здесь мой пример использования

soapserver.php:

<?php

$data = file_get_contents('php://input');
$fp = fopen('data.txt', 'a');
fwrite($fp, json_encode($data));
fwrite($fp, "\n");

class MySoapServer {

    public function addNumbers($num1, $num2) {
        return $num1 + $num2;
    }

}

$options = array('uri' => 'http://test.local/');
$server = new SoapServer(NULL, $options);
$server->setClass('MySoapServer');

ob_end_clean();
header("Connection: close\r\n");
ignore_user_abort(true);
ob_start();
$server->handle();
$soapXml = ob_get_contents();
$size = ob_get_length();
// Flush (send) the output buffer and turn off output buffering
ob_end_clean();
ob_start();
header("Content-Length: $size");
echo $soapXml;
ob_end_flush();
// Unless both are called !
flush();
// Continue do another process after sent message
//example
sleep(10);
fwrite($fp, "Test Writing\n");
fclose($fp);

?>

soapclient.php:

<?php

// client
$options = array(
    'location' => 'http://test.local/stack/soapserver.php',
    'uri' => 'http://test.local/stack/soapserver.php'
);

$client = new SoapClient(NULL, $options);
echo $client->addNumbers(3, 5); //  8

Вы сразу увидите ответ 8 в broswer. Через 10 секунд вы увидите Test Writing в файле data.txt

Ответ 3

Если вы пишете SOAP-сервис, почему бы просто не использовать SoapService вместе с методом handle()? Вам не нужно будет фактически выполнять рукопожатие TCP (отправка ACK), а что нет. Я почти уверен, что все это было для вас в этих классах/методах.

Контекста нет, так что это моя лучшая образованная догадка.