PHP异步SOAP服务器是否在响应之前发送确认消息?

11

我不确定如何做这件事。我编写的SOAP服务规范要求它在响应请求之前发送确认消息。

在PHP中如何实现这一点?我没有看到任何关于如何实现这个功能的例子。

来自需求文档:

每次提交Inv消息请求时,Integration Partner向Vendor发送一个确认消息。Vendor也会从每个RequestInv消息响应中向集成合作伙伴发送单个确认消息。

这不是标准的TCP ack响应。它是一个自定义的SOAP格式响应,用于确认请求已被接收。请参见下面的示例。

询问供应商后:

他们声称这是一个遗留系统,并且它是按照该流程编写的。他们现在无法更改它。我告诉他,在20多年的编程经验中,我从未见过任何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>

确切措辞是什么?你确定他们不是需要某种Ping端点来检查连接状态吗? - delboy1978uk
该服务将处理一个 RequestInv 请求,并用 SubmitInv 响应对该请求进行回复。 - MB34
这是我实现它的方式:SOAP请求(SOAP消费者)-> ACK(SOAP服务器)[服务在线,请求良好/不良并且正在处理] -> ACK(SOAP消费者)[准备接收响应] -> SOAP响应(SOAP服务器) - Giacomo Penuti
你确定那是应用级别的需求,而不是传输级别的吗?(我在谈论 TCP 连接中的 ACK) - Daniel Protopopov
@DanielProtopopov,看一下我在规格说明中的评论。我认为他们在谈论异步操作。 - MB34
显示剩余13条评论
3个回答

4

好的,我已经在我的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>";
    }

}

是的,我知道,我已经标记了这个问题并请求管理员协助,因为FoxVSky修改了他的答案,使用了我发布的基本相同的技术。 - MB34

0

你可以使用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。10秒后,你将在文件data.txt中看到Test Writing


你能否重新展示一下如何在SOAP事务中完成吗?我不希望你因为这个答案而得到赏金。 - MB34
如果您将使用标准的SoapServer,您可以在回显测试字符串的位置插入对其handle方法的调用。 (http://php.net/manual/ru/soapserver.handle.php) - Daniel Protopopov
@DanielProtopopov,我不明白你在这里说什么。 - MB34
@FoxVSky,你的解决方案在SOAP事务中不起作用,即使你没有展示如何使用它。 - MB34
我是说他的示例可以与标准股票SoapServer一起使用,以稍微不同的方式实现您想要的内容。 - Daniel Protopopov
此解决方案已经进行了测试,结果不尽如人意。 - MB34

0
如果您正在编写SOAP服务,为什么不直接使用SoapServerhandle()方法呢?您不需要实际实现TCP握手(发送ACK)等操作。我相信这些类/方法已经为您处理好了所有操作。
由于缺乏上下文,这是我最好的猜测。

我不太明白。你是想让我解释一下 handle() 方法的工作原理吗? - Quetzy Garcia
不,我希望您能解释您的意思。我知道handle方法的工作原理,您暗示要使用它来处理ACK,但这并不适合这种情况。 - MB34
你应该编辑你的问题,添加在评论中讨论过的其他相关信息,以便人们帮助你。正如我所说,你不应该需要处理协议细节。我甚至不确定在PHP中你会如何做到这一点,除非你开始打开套接字。我的意思是,已经有一个SOAP服务器类可以为你处理所有这些事情。 - Quetzy Garcia
需要的不是标准的SOAP,那么你为什么认为标准的SOAP类应该处理它呢? - MB34
TCP是标准SOAP(或任何其他应用程序协议)使用的,可以透明地处理它。我现在已经阅读了所有评论,老实说,这是一个非常混乱的实现。SOAP是应用层,而要发送“ACK”,您将不得不处理传输层(TCP)。 - Quetzy Garcia
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接