如何填充复合消息并作为 SoapServer 响应 XML 返回?

Posted

技术标签:

【中文标题】如何填充复合消息并作为 SoapServer 响应 XML 返回?【英文标题】:How populate composite message and return as SoapServer response XML? 【发布时间】:2012-09-28 03:32:54 【问题描述】:

我正在设置一个 SOAP Web 服务,它应该返回一个复合消息。 此消息的有效实例如下:

<dl190Response xmlns="http://pse/">
    <cdhead cisprik="5563167"/>
    <mvts>
        <mvts_S att="a1">
            <x>x1</x>
            <w>w1</w>
        </mvts_S>
        <mvts_S>
            <x>x2</x>
            <w>w2</w>
        </mvts_S>
    </mvts>
</dl190Response>

所有这些都在 wsdl 中整齐地定义:

<?xml version="1.0" encoding="UTF-8"?>
<definitions
    xmlns="http://schemas.xmlsoap.org/wsdl/"
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:tns="http://pse/"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    name="PSE"
    targetNamespace="http://pse/">
    <types>
        <xs:schema xmlns="http://pse/" targetNamespace="http://pse/">
            <xs:complexType name="cdhead_T">
                <xs:attribute name="cisprik" type="xs:long"/>
            </xs:complexType>
            <xs:complexType name="mvts_T">
                <xs:sequence>
                    <xs:element name="mvts_S" type="mvts_S_T" minOccurs="0" maxOccurs="unbounded"/>
                </xs:sequence>
            </xs:complexType>
            <xs:complexType name="mvts_S_T">
                <xs:sequence>
                    <xs:element name="x" type="xs:string"/>
                    <xs:element name="w" type="xs:string"/>
                </xs:sequence>
                <xs:attribute name="att" type="xs:string" use="optional"/>
            </xs:complexType>
        </xs:schema>
    </types>
    <message name="DL190Req">
        <part name="cdhead" type="tns:cdhead_T"/>
    </message>
    <message name="DL190Res">
        <part name="cdhead" type="tns:cdhead_T"/>
        <part name="mvts" type="tns:mvts_T"/>
    </message>
    <portType name="DLPortType">
        <operation name="dl190">
            <input message="tns:DL190Req"/>
            <output message="tns:DL190Res"/>
        </operation>
    </portType>
    <binding name="DLBinding" type="tns:DLPortType">
        <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
        <operation name="dl190">
            <soap:operation soapAction="http://www.testServer.com/test_soap.php#dl190"/>
            <input>
                <soap:body use="literal" namespace="http://pse/"/>
            </input>
            <output>
                <soap:body use="literal" namespace="http://pse/"/>
            </output>
        </operation>
    </binding>
    <service name="PSE">
        <port name="DLPortType" binding="tns:DLBinding">
            <soap:address location="http://www.testServer.com/test_soap.php"/>
        </port>
    </service>
</definitions>

我一直在无休止地在服务器端 test_soap.php 上工作以使其正确,但我没有成功。 在返回 XML 之前正常工作的部分内容如下:

<?php
    class PSE 
        function dl190 ($arg) 
            //I don't need to extract the input data just now

            mysql_connect('127.0.0.1:3306', 'user', 'password');
            mysql_select_db('myDatabase');

            $xml = new SimpleXMLElement('<dl190Res/>');
            $xml -> addChild('cdhead');
            $mvts = $xml -> addChild('mvts');

            $rows = mysql_query('select * from trx');
            while($data = mysql_fetch_assoc($rows)) 
                $mvts_S = $mvts -> addChild('mvts_S'); 
                foreach($data as $key => $value) 
                    if ($key == 'att')  $mvts_S -> addAttribute($key, $value);
                    else    $mvts_S -> addChild($key, $value);
                
            ;

            $dom = dom_import_simplexml ($xml) -> ownerDocument;

            // now respond to the request and return the XML
        

    ;
    ini_set( "soap.wsdl_cache_enabled", "0");
    $server = new SoapServer ("test.wsdl");
    $server -> setClass ('PSE');
    $server -> setObject (new PSE());
    $server -> handle();    
?>

我几乎尝试了我能想到的所有方法来得到正确的响应,但我没有成功。我能够对之前仅包含一个部分的消息执行相同的操作(请参阅我最近的问题+答案)。 但是在这里,有两个消息部分,我没有成功。

$xml 内容的调试表明它正是我希望看到的返回,当然,在让肥皂服务器将其包装到 Envelope+Body 之后。

实际上,这种情况与只有一个消息部分的情况不同:只要我先剥离 XML 声明,然后返回它,我就可以从一个部分创建一个新的 SoapVar。在这里我不能这样做,因为返回值由两部分组成。

所以我想知道我现在应该做什么:

    为响应消息声明一个类并填充并返回它 使用 SoapVar 和/或 SoapParam 执行一些魔术(请注意,我已经尝试了很多) 使用数组和 SoapVar 执行一些魔术(已经尝试了很多) 不知何故(如何?)向 wsdl 寻求帮助 完全不同的东西 用 SoapServer 摆脱这整个噩梦,从头开始创建我自己的 http 响应

感谢所有帮助,所以所有肥皂专家,不要犹豫,尝试回答这个问题!

添加

作为临时解决方法,我编辑了 WSDL,将响应消息更改为只有一个部分。这允许我将预期的消息作为预期的两个部分的串联传递(或任何其他消息,因为 SoapVar 没有对返回的值进行任何消息定义的结构 WSDL 检查):

$xml1 = new SimpleXMLElement('<cdhead/>');
$xml1 -> addAttribute ('xmlns', 'http://pse/');
$xml1 -> addAttribute ('cisprik', $newCisprik);

$xml2 = new SimpleXMLElement('<mvts/>');

$rows = mysql_query('select * from trx');
while($data = mysql_fetch_assoc($rows)) 
    $mvts_S = $xml2 -> addChild('mvts_S'); 
    foreach($data as $key => $value) 
        if ($key == 'att')  $mvts_S -> addAttribute($key, $value);
        else    $mvts_S -> addChild($key, $value);
    
;

$dom1 = dom_import_simplexml ($xml1) -> ownerDocument;
$dom2 = dom_import_simplexml ($xml2) -> ownerDocument;
$part1 = $dom1 -> saveXML($dom1 -> documentElement);
$part2 = $dom2 -> saveXML($dom2 -> documentElement);

$result = new SoapVar ($part1 . $part2, XSD_ANYXML);

这方面的特殊之处在于,连接当然不是有效的 XML,缺少周围的根元素,但 SoapVar 无论如何都能解析它。

就是这样:任何对 SoapVar 和 SoapParam / SoapServer 有详细了解的人都可以解释是否有可能返回两个消息部分吗? 并解释如何做到这一点? 或者,提供有关如何在其他 SOAP 设置中执行此操作的详细信息?

【问题讨论】:

【参考方案1】:

我尝试并设置了您的最小 SoapServer,这就是我所做的:

    将 wsdl 和 PHP 脚本复制到一个文件夹中。 更改 wsdl 位置引用以指向 php 脚本。 已将 WSDL 导入 SoapUI - 强烈推荐,它是免费的! 尝试调用服务。

这是我的呼叫请求:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:pse="http://pse/">
   <soapenv:Header/>
   <soapenv:Body>
      <pse:dl190>
         <cdhead cisprik="0"/>
      </pse:dl190>
   </soapenv:Body>
</soapenv:Envelope>

由于您对数据库的调用,它最初无法正常工作,但我了解您确实只需要一个如何在肥皂层上正确响应的解决方案,其余的您会弄清楚。

这是一个简单的解决方案:

<?php
class PSE

    public function dl190($arg)
    
        //var_dump($arg) is:
        //object(stdClass)#3 (1) 
        //   ["cisprik"]=> int(0)
        //

        $fakeResult = array();

        $fakeResult[0] = new stdClass();
        $fakeResult[0]->cisprik = 23;

        $fakeResult[1] = array();

        $fakeResult[1][0] = new stdClass();
        $fakeResult[1][0]->att = "a1";
        $fakeResult[1][0]->x = "x1";
        $fakeResult[1][0]->w = "w1";

        $fakeResult[1][1] = new stdClass();
        //$fakeResult[1][1]->att = "a1";
        $fakeResult[1][1]->x = "x2";
        $fakeResult[1][1]->w = "w2";

        return $fakeResult;
    


//ini_set("soap.wsdl_cache_enabled", "0");

$server = new SoapServer ("wsdl.xml");
$server->setObject(new PSE());
$server->handle();

请注意,PHP 基本上只在请求参数中发出 stdClass 和数组的混合(我将您得到的内容作为注释转储在顶部)。这是一件可悲的事情,但我相信在同一级别上做出响应是公平的事情,而不是通过使用 XML 作为回溯方式使事情变得更糟。

如果你对这段代码执行上面的请求,你会得到这个soap响应:

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://pse/">
   <SOAP-ENV:Body>
      <ns1:dl190Response>
         <cdhead cisprik="23"/>
         <mvts>
            <mvts_S att="a1">
               <x>x1</x>
               <w>w1</w>
            </mvts_S>
            <mvts_S>
               <x>x2</x>
               <w>w2</w>
            </mvts_S>
         </mvts>
      </ns1:dl190Response>
   </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

但仍有改进的余地。 PHP SoapServer(以及 SoapClient)有一个名为 classmap 的功能,我强烈建议您使用它。如果您的 IDE 支持任何类型的 PHPDoc 自动补全,那么您几乎可以在任何处理正确设置值的地方利用它。

这是我的带有类映射定义的版本。请注意,我为它们都加上了“PSE”前缀,以强调类名不需要以 WSDL 中的 complexTypes 命名。

<?php
class PSE

    public function dl190(PSE_cdhead_T $arg)
    
        //  var_dump($arg) is:
        //  object(PSE_cdhead_T)#3 (1) 
        //      ["cisprik"]=> int(0)
        //  

        $fakeResult = array();
        $fakeResult[0] = new PSE_cdhead_T();
        $fakeResult[0]->cisprik = 23;

        $fakeResult[1] = array();

        $fakeResult[1][0] = new PSE_mvts_S_T();
        $fakeResult[1][0]->att = "a1";
        $fakeResult[1][0]->x = "x1";
        $fakeResult[1][0]->w = "w1";

        $fakeResult[1][1] = new PSE_mvts_S_T();
        //$fakeResult[1][1]->att = "a1";
        $fakeResult[1][1]->x = "x2";
        $fakeResult[1][1]->w = "w2";

        return $fakeResult;
    


class PSE_cdhead_T 
    /**
     * @var int
     */
    public $cisprik;



class PSE_mvts_S_T 
    /**
     * @var string
     */
    public $att;

    /**
     * @var string
     */
    public $x;

    /**
     * @var string
     */
    public $w;


//ini_set("soap.wsdl_cache_enabled", "0");

$classmap = array(
    'cdhead_T' => 'PSE_cdhead_T',
    'mvts_S_T' => 'PSE_mvts_S_T',
);

$serverOptions = array(
    'encoding' => 'utf-8',
    'classmap' => $classmap,
    'features' => SOAP_SINGLE_ELEMENT_ARRAYS,
);

$server = new SoapServer ("wsdl.xml", $serverOptions);

$server->setObject(new PSE());
$server->handle();

不幸的是,一个恼人的问题没有解决:在你的回复中,你不能使用一个类,但必须使用一个数组,而没有任何关于哪个索引参数映射到哪个 xml 结果的提示。这真的很糟糕,但要改变它,你必须改变 WSDL。

我很遗憾地报告说我不是创建 WSDL 文件的专家。我尝试添加一个复杂类型作为响应中的唯一元素。如果您查看我的第二个版本中的转储,您会看到您获得了一个类 PSE_cdhead_T,它是请求消息的唯一部分的映射 complexType。

因为响应消息有两部分,SoapServer 必须将它们放入一个数组中。没有可能的命名引用。我建议您在这里添加一个新的 complexType,并在地图中相应地创建一个新类,如下所示:

class PSE_DL190_Response

    /**
     * @var PSE_cdhead_T
     */
    public $cdhead;
    /**
     * @var PSE_mvts_S_T[]
     */
    public $mvts;

然后您可以更轻松地准备响应:

$fakeResult = new PSE_DL190_Response();
$fakeResult->cdhead = new PSE_cdhead_T(); // Set the one cdhead structure
$fakeResult->mvts[] = new PSE_mvts_S_T(); // Add one mvts structure;

这很可能会导致您的 XML 响应发生变化 - 但我无法评估影响。

最后一个想法:有一些 PHP 的 WSDL 代码生成器,您可以尝试一下。他们将自动生成类映射所需的类。上次我尝试它们时,它们似乎可以工作,但不适用于我测试的所有 WSDL 文件。 Soap 的定义似乎太复杂了,无法做到这一点。但如果它有效,那么它是值得的,而不是手动创建它们。

【讨论】:

听起来不错,我将在今天晚些时候对此进行彻底的研究。我觉得赏金即将到来:-) OK 测试了它,一切正常 - 非常感谢 - 我会尝试将其扩展到现实中更复杂的问题,但至少我现在有一个先机!我终于看到了 wsdl 细节如何影响返回元素的名称值——我也想知道这很好。【参考方案2】:

我不是 SOAP 专家,但不得不在一些项目上使用 SOAP 来与 3rd 方服务器交互是一场噩梦,部分原因是服务器实现不太好,而且我自己作为菜鸟无知。但我记得在尝试按原样使用PHP SOAP 类时遇到了很多问题,然后我改用NuSOAP toolkit,它更容易完成工作并解决了我遇到的许多奇怪问题。所以我的建议是使用像 NuSOAP 这样的工具包,看看是否更有意义。

SOAP is an old spec 这还不错,但我认为它不再被处理了(WG closed 2009-07-10)而且它很脏而且使用起来很痛苦。 Microsoft SOAP toolkit 甚至已被弃用和退役。所以是的,如果你能走另一条路,那就去做吧,我会的。

也许可以走RESTful 路线。

REST 通过允许不同服务之间的松散耦合来促进 Web 服务器之间的事务。 REST 的类型没有它的对应物 SOAP 强。 REST 语言基于名词和动词的使用,并强调可读性。与 SOAP 不同,REST 不需要 XML 解析,也不需要往返于服务提供者的消息头。这最终会使用更少的带宽。 REST 错误处理也不同于 SOAP。

【讨论】:

当然,也必须有一种方法可以使用 SoapServer 实现我的目标 - 我希望先走这条路。此外,我需要一个更详细的答案,包含正确方向的示例代码,或者要遵循的步骤的详细列表。而且我绝对需要响应是 SOAP 类型的,因此 XML 带有(无标题)信封/正文/响应/两个子消息部分。

以上是关于如何填充复合消息并作为 SoapServer 响应 XML 返回?的主要内容,如果未能解决你的问题,请参考以下文章

Hibernate 不填充 AUTO_INCREMENT 列作为复合 PK、错误或反功能的一部分?

如何使用 JSON 数据填充下拉列表作为 jQuery 中的 ajax 响应

在 Groovy 上使用 JSON 响应并使用响应填充 Map

从 Zend_Form 获取错误消息并作为 json 响应

Kafka 是不是支持请求响应消息传递

EDI X12 版本 4010 消息 - 非复合元素可以包含子元素分隔符吗?