SOAP是PHP开发RPC数据通讯的常用方式,有WSDL和NON-WSDL两种模式。这里主要介绍WSDL模式RPC深层工作原理。以下是引用维基百科的两段说明:

WSDL(Web服务描述语言,Web Services Description Language)是为描述Web服务发布的XML格式。

WSDL描述Web服务的公共接口。这是一个基于XML的关于如何与Web服务通讯和使用的服务描述;也就是描述与目录中列出的Web服务进行交互时需要绑定的协议和信息格式。通常采用抽象语言描述该服务支持的操作和信息,使用的时候再将实际的网络协议和信息格式绑定给该服务。

使用PHP创建SOAP-WSDL服务

HelloServerWsdl.php:

 1 <?php
 2 
 3 function hello($someone) { 
 4     #return file_get_contents('php://input');
 5     #return print_r($_SERVER, true);
 6     return "Hello " . $someone . "! - With WSDL";
 7 } 
 8 
 9 ini_set('soap.wsdl_cache_enabled', 0); 
10 $server = new SoapServer(
11     'http://php.laijinman.com/soap/Hello.wsdl', 
12     [
13         'soap_version' => SOAP_1_2,
14     ]
15 );
16 $server->addFunction('hello'); 
17 $server->handle();

Hello.wsdl:

 1 <?xml version="1.0"?>
 2 <definitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns="http://schemas.xmlsoap.org/wsdl/">
 3 
 4   <message name="req">
 5     <part name="someone" type="xsd:string"/>
 6   </message>
 7 
 8   <message name="res">
 9     <part name="return" type="xsd:string"/>
10   </message>
11 
12   <portType name="portTypeName">
13     <operation name="hello">
14       <input message="tns:req"/>
15       <output message="tns:res"/>
16     </operation>
17   </portType>
18 
19   <binding name="bindingName" type="tns:portTypeName">
20     <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
21     <operation name="hello">
22       <soap:operation soapAction=""/>
23       <input>
24         <soap:body use="encoded" namespace="urn:inputNamespace" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
25       </input>
26       <output>
27         <soap:body use="encoded" namespace="urn:outputNamespace" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"/>
28       </output>
29     </operation>
30   </binding>
31 
32   <service name="serviceName">
33     <port name="portName" binding="tns:bindingName">
34       <soap:address location="http://php.laijinman.com/soap/HelloServerWsdl.php"/>
35     </port>
36   </service>
37 
38 </definitions>

HelloClientWsdl.php:

 1 <?php
 2 ini_set('soap.wsdl_cache_enabled', 0); 
 3 
 4 class Client extends SoapClient
 5 {
 6 
 7     public function __doRequest($request, $location, $action, $version, $one_way = NULL) {
 8         #var_dump(func_get_args());
 9         #die();
10 
11         return parent::__doRequest($request, $location, $action, $version, $one_way);
12     }
13 }
14 
15 $client = new SoapClient(
16 #$client = new Client(
17     'http://php.laijinman.com/soap/Hello.wsdl',
18     [
19         'soap_version' => SOAP_1_2,
20         'trace' => 1,
21     ]
22 );
23 
24 var_dump($client->__getFunctions());
25 var_dump($client->__getTypes());
26 
27 $res = $client->hello('world');
28 #$res = $client->__soapCall('hello', ['world']);
29 var_dump($res);
30 
31 var_dump($client->__getLastRequestHeaders());
32 var_dump($client->__getLastRequest());
33 var_dump($client->__getLastResponseHeaders());
34 var_dump($client->__getLastResponse());

运行程序http://php.laijinman.com/soap/HelloClientWsdl.php,输出结果:

/home/laijinman/data/websites/php/soap/HelloClientWsdl.php:24:
array (size=1)
  0 => string 'string hello(string $someone)' (length=29)

/home/laijinman/data/websites/php/soap/HelloClientWsdl.php:25:
array (size=0)
  empty

/home/laijinman/data/websites/php/soap/HelloClientWsdl.php:29:string 'Hello world! - With WSDL' (length=24)

/home/laijinman/data/websites/php/soap/HelloClientWsdl.php:31:string 'POST /soap/HelloServerWsdl.php HTTP/1.1
Host: php.laijinman.com
Connection: Keep-Alive
User-Agent: PHP-SOAP/7.0.14
Content-Type: application/soap+xml; charset=utf-8; action=""
Content-Length: 453

' (length=204)

/home/laijinman/data/websites/php/soap/HelloClientWsdl.php:32:string '<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:ns1="urn:inputNamespace" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:enc="http://www.w3.org/2003/05/soap-encoding"><env:Body><ns1:hello env:encodingStyle="http://www.w3.org/2003/05/soap-encoding"><someone xsi:type="xsd:string">world</someone></ns1:hello></env:Body></env:Envelope>
' (length=453)

/home/laijinman/data/websites/php/soap/HelloClientWsdl.php:33:string 'HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Sat, 24 Dec 2016 06:43:32 GMT
Content-Type: application/soap+xml; charset=utf-8
Content-Length: 565
Connection: close
X-Powered-By: PHP/7.0.14

' (length=193)

/home/laijinman/data/websites/php/soap/HelloClientWsdl.php:34:string '<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:ns1="urn:outputNamespace" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:enc="http://www.w3.org/2003/05/soap-encoding"><env:Body xmlns:rpc="http://www.w3.org/2003/05/soap-rpc"><ns1:helloResponse env:encodingStyle="http://www.w3.org/2003/05/soap-encoding"><rpc:result>return</rpc:result><return xsi:type="xsd:string">Hello world! - With WSDL</return></ns1:helloResponse></env:Body></env:Envelope>
' (length=565)

分析

SOAP-WSDL RPC实际上是基于HTTP协议,向服务端http://php.laijinman.com/soap/HelloServerWsdl.php以POST方式发送了一段XML数据。

主要可以看两个关键点:

向服务器发送XML数据(var_dump($client->__getLastRequest());):

1 <?xml version="1.0" encoding="UTF-8"?>
2 <env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:ns1="urn:inputNamespace" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:enc="http://www.w3.org/2003/05/soap-encoding">
3   <env:Body>
4     <ns1:hello env:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
5       <someone xsi:type="xsd:string">world</someone>
6     </ns1:hello>
7   </env:Body>
8 </env:Envelope>

接收服务器返回的XML数据(var_dump($client->__getLastResponse());):

1 <?xml version="1.0" encoding="UTF-8"?>
2 <env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:ns1="urn:outputNamespace" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:enc="http://www.w3.org/2003/05/soap-encoding">
3   <env:Body xmlns:rpc="http://www.w3.org/2003/05/soap-rpc">
4     <ns1:helloResponse env:encodingStyle="http://www.w3.org/2003/05/soap-encoding">
5       <rpc:result>return</rpc:result>
6       <return xsi:type="xsd:string">Hello world! - With WSDL</return>
7     </ns1:helloResponse>
8   </env:Body>
9 </env:Envelope>
  • 客户端把RPC函数调用封装成一个XML格式的字符串,通过HTTP调用POST到服务器的接口端。
  • 服务器接收到XML数据,解析内容,并调用对应的接口进行处理,把结果再次封装成XML内容,并输出。
  • 客户端把服务器端输出的XML内容,并解析出返回结果,并返回给函数调用者。

其它方式实现

通过分析SOAP的底层实现原理知道,所谓的SOAP RPC调用,其实就是向服务器以HTTP+POST方式发送一段XML数据,并获取到服务器端输出到一段XML数据,通过解析XML就可以获取到需要的调用结果了。

所以重点来了——如果我们了解了这些XML与服务对应的关系和内容,我们就可以不依赖soapClient进行SOAP的RPC通讯了,这对于其它的语言实际SOAP RPC服务和调用很有帮助。

这里举例使用其它方式调用SOAP RPC的方法:

  • php+curl方式:
 1 <?php
 2 
 3 $ch = curl_init();
 4 
 5 $req = '<?xml version="1.0" encoding="UTF-8"?>
 6 <env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:ns1="urn:inputNamespace" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:enc="http://www.w3.org/2003/05/soap-encoding"><env:Body><ns1:hello env:encodingStyle="http://www.w3.org/2003/05/soap-encoding"><someone xsi:type="xsd:string">world</someone></ns1:hello></env:Body></env:Envelope>';
 7 
 8 $opts = [
 9     CURLOPT_URL => 'http://php.laijinman.com/soap/HelloServerWsdl.php',
10     CURLOPT_RETURNTRANSFER => 1,
11     CURLOPT_CUSTOMREQUEST => 'POST',
12     CURLOPT_POSTFIELDS => $req,
13 ];
14 
15 curl_setopt_array($ch, $opts);
16 $res = curl_exec($ch);
17 
18 var_dump($res);

输出:

/home/laijinman/data/websites/php/soap/curl.php:18:
string(565) "<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:ns1="urn:outputNamespace" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:enc="http://www.w3.org/2003/05/soap-encoding"><env:Body xmlns:rpc="http://www.w3.org/2003/05/soap-rpc"><ns1:helloResponse env:encodingStyle="http://www.w3.org/2003/05/soap-encoding"><rpc:result>return</rpc:result><return xsi:type="xsd:string">Hello world! - With WSDL</return></ns1:helloResponse></env:Body></env:Envelope>
"
  • shell+curl方式:
1 curl http://php.laijinman.com/soap/HelloServerWsdl.php \
2 -d '<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:ns1="urn:inputNamespace" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:enc="http://www.w3.org/2003/05/soap-encoding"><env:Body><ns1:hello env:encodingStyle="http://www.w3.org/2003/05/soap-encoding"><someone xsi:type="xsd:string">world</someone></ns1:hello></env:Body></env:Envelope>'

输出:

<?xml version="1.0" encoding="UTF-8"?>
<env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope" xmlns:ns1="urn:outputNamespace" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:enc="http://www.w3.org/2003/05/soap-encoding"><env:Body xmlns:rpc="http://www.w3.org/2003/05/soap-rpc"><ns1:helloResponse env:encodingStyle="http://www.w3.org/2003/05/soap-encoding"><rpc:result>return</rpc:result><return xsi:type="xsd:string">Hello world! - With WSDL</return></ns1:helloResponse></env:Body></env:Envelope>