java 使用 spring webservice 发布的 service,由php使用SoapClient调用。

遇到有一个奇怪的现象,java调用发布的webservice,没有问题,使用工具SOAP UI调用,没有问题,

同样的php代码调用google的天气服务没问题,调用我们的wsdl就不行。

经过摸索调试,发现如下问题,以及解决的过程。

 

首先发布的wsdl如下:

 

Xml代码  
  1. xml version="1.0" encoding="UTF-8" standalone="no"?>  
  2. <wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:sch="http://www.phpxiaoxin.com/doorway" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://www.phpxiaoxin.com/doorway" targetNamespace="http://www.phpxiaoxin.com/doorway">  
  3.   <wsdl:types>  
  4.     <s:schema xmlns:dw="http://www.phpxiaoxin.com/doorway" xmlns:s="http://www.w3.org/2001/XMLSchema" attributeFormDefault="unqualified" elementFormDefault="qualified" targetNamespace="http://www.phpxiaoxin.com/doorway">  
  5.       <s:complexType name="Error">  
  6.         <s:attribute name="Code" type="s:string" use="required"/>  
  7.         <s:attribute name="Message" type="s:string" use="required"/>  
  8.       s:complexType>  
  9.       <s:simpleType name="Status">  
  10.         <s:restriction base="s:string">  
  11.           <s:enumeration value="Successful"/>  
  12.           <s:enumeration value="Failed"/>  
  13.         s:restriction>  
  14.       s:simpleType>  
  15.       <s:complexType abstract="true" name="BaseRequest">  
  16.         <s:attribute name="Token" type="s:string" use="required"/>  
  17.         <s:attribute name="UserName" type="s:string" use="required"/>  
  18.         <s:attribute name="Password" type="s:string" use="required"/>  
  19.       s:complexType>  
  20.       <s:complexType abstract="true" name="BaseResponse">  
  21.         <s:sequence>  
  22.           <s:element minOccurs="0" name="Error" type="dw:Error"/>  
  23.         s:sequence>  
  24.         <s:attribute name="Token" type="s:string" use="required"/>  
  25.         <s:attribute name="Status" type="dw:Status" use="required"/>  
  26.       s:complexType>  
  27.       <s:complexType name="StatusResponse">  
  28.         <s:complexContent>  
  29.           <s:extension base="dw:BaseResponse"/>  
  30.         s:complexContent>  
  31.       s:complexType>  
  32.         <s:attribute name="RatePlanCode" type="s:string" use="required"/>  
  33.         <s:attribute name="RoomTypeCode" type="s:string" use="required"/>  
  34.         <s:attribute name="NumberOfUnits" type="s:int" use="required"/>  
  35.         <s:attribute name="HotelCode" type="s:string" use="required"/>  
  36.         <s:attribute name="DistributorReservationId" type="s:string" use="required"/>  
  37.       s:complexType>  
  38.       <s:element name="PingRequest">  
  39.         <s:complexType>  
  40.           <s:complexContent>  
  41.             <s:extension base="dw:BaseRequest">  
  42.               <s:attribute name="Echo" type="s:string" use="required"/>  
  43.             s:extension>  
  44.           s:complexContent>  
  45.         s:complexType>  
  46.       s:element>  
  47.       <s:element name="PingResponse">  
  48.         <s:complexType>  
  49.           <s:complexContent>  
  50.             <s:extension base="dw:StatusResponse">  
  51.               <s:attribute name="Echo" type="s:string" use="required"/>  
  52.             s:extension>  
  53.           s:complexContent>  
  54.         s:complexType>  
  55.       s:element>  
  56.     s:schema>  
  57.   wsdl:types>  
  58.   <wsdl:message name="PingResponse">  
  59.     <wsdl:part element="tns:PingResponse" name="PingResponse">  
  60.     wsdl:part>  
  61.   wsdl:message>  
  62.   <wsdl:message name="PingRequest">  
  63.     <wsdl:part element="tns:PingRequest" name="PingRequest">  
  64.     wsdl:part>  
  65.   wsdl:message>  
  66.   <wsdl:portType name="Doorway">  
  67.     <wsdl:operation name="Ping">  
  68.       <wsdl:input message="tns:PingRequest" name="PingRequest">  
  69.     wsdl:input>  
  70.       <wsdl:output message="tns:PingResponse" name="PingResponse">  
  71.     wsdl:output>  
  72.     wsdl:operation>  
  73.   wsdl:portType>  
  74.   <wsdl:binding name="DoorwaySoap11" type="tns:Doorway">  
  75.     <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>  
  76.     <wsdl:operation name="Ping">  
  77.       <soap:operation soapAction=""/>  
  78.       <wsdl:input name="PingRequest">  
  79.         <soap:body use="literal"/>  
  80.       wsdl:input>  
  81.       <wsdl:output name="PingResponse">  
  82.         <soap:body use="literal"/>  
  83.       wsdl:output>  
  84.     wsdl:operation>  
  85.   wsdl:binding>  
  86.   <wsdl:service name="HotelDoorwayService">  
  87.     <wsdl:port binding="tns:DoorwaySoap11" name="DoorwaySoap11">  
  88.       <soap:address location="/soap/doorway/"/>  
  89.     wsdl:port>  
  90.   wsdl:service>  
  91. wsdl:definitions>  

 

其中定义了一个方法"Ping" 输入为PingRequest 和 PingResponse 其中PingRequest包含一个echo的属性,并集成一个BaseRequest的对象。

 

下面是使用php调用的代码:

 

Php代码  
  1. ini_set("soap.wsdl_cache_enabled""0"); // disabling WSDL cache  
  2. $wsdl="http://10.10.10.10:8888/doorway/soap/doorway/doorway.wsdl";  
  3. $soap=new SoapClient($wsdlarray'trace'=>true,'cache_wsdl'=>WSDL_CACHE_NONE, 'soap_version'   => SOAP_1_1));  
  4. $method="Ping";  
  5. $params = array('Token'=>'E30ED3AA-65DE-48F9-BEA4-BA021B119625','UserName'=>'cccc','Password'=>'pppp''Echo'=>'hello');  
  6. try{  
  7. $result=$soap->$method($params);  
  8. }catch(Exception $e) {  
  9.     echo "Exception: " . $e->getMessage();  
  10. }  
  11. //$result为stdClass类型,因此不能使用 echo $result的方式输出,会报错的。  
  12. echo $result->Echo;  
  13. ?>  

 

最初我们调用的时候获取到的错误是找不到Ping方法,这个后来不确定是如何解决的。

后来加了SoapClient 的调用参数WSDL_CACHE_NONE让soap调用的时候不缓存wsd,以避免服务器修改了wsdl无法及时更新。设置此参数后,每次调用,都会重 新load wsdl文件,因此监控java的log,将会拿到一个get请求,起初我们以为是调用wsdl的请求,后来发现,他是拿wsdl的请求。

 

过了这关后,会遇到如下错误:Unable to parse URL

 

这个原因经过google之后发现问题出在wsdl上面:

这里有的wsdl会:

这种方式使用java调用是没有问题的,但是使用php调用就是不行。再加上错误的提示信息,可以理解为,SoapClient没有智能的解析这个 location,因此无法调用到soap的地址(这里称之为地址,其实我也不知道是什么意思,要想搞懂的可以看wsdl的协议。)

 

按照往上介绍的方法,在SoapClient调用里面增加参数:location =>"http://www.phpxiaoxin.com/soap/doorway"

但这种方法显然是不太好的,更好的方法是在生成的wsdl中就将location参数直接设置成绝对的url地址不要是相对的,也不要是空。

 

另外需要注意一个细节就是soap_version,分为:SOAP_1_1,SOAP_1_2 这两者发送的header是不一样的,一个是:text/xml一个是soap/xml,有的时候不兼容,就会将请求拒绝掉。

 

最后还有一个问题就是组装调用方法参数:

所调用方法应该组装的参数,主要看方法对应的wsdl中的input,对于Ping方法来说,就是PingRequest。

pingRequest继承了一个BaseRequest,那么相当于PingRequest有:token、username、password,以及自身的echo,因此将这些属性直接组装成array塞进去就ok了。

 

这里没有实验过一个方法对应多个输入对象应该如何处理,大家可以自己去试。而对已简单到String类型的输入参数来说,我这边的param为:$params = array('arg0'=>"hello111");

不过我的这个我使用的是另外一个wsdl,其输入参数的wsdl为(使用spring+cxf+aegis数据绑定生成):

 

Xml代码  
  1. <xsd:complexType name="sayHello">  
  2.     <xsd:sequence>  
  3.     <xsd:element minOccurs="0" name="arg0" nillable="true" type="xsd:string"/>  
  4.     xsd:sequence>  
  5. xsd:complexType>  
 

大家可以看看对应关系。是否能看出点什么规律。而对于复杂的对象,则可以层层的array进行嵌套。可以参开这个文章:PHP SOAP如何传入复杂对象

 

 

最后提醒一下:

php使用webservice的时候,需要确认开启了php_soap、php_curl的扩展(php.ini)

关于php的soap client 以及其option参数,可以参考官网:http://www.php.net/manual/en/soapclient.soapclient.php

 

使用php最好配置上xdebug以便可以调试,看到对象的值,相当明了,

SoapClient里面有发送的request的xml一看就知道是否有问题,以及问题出在哪里。

详情可以看一下xdebug等文章。

 

再就是php调用的时候我增加了try,catch,并且将exception输出,这样输出的结果似乎比直接跑异常要详细。

因此建议大家遇到问题的时候可以catch一下,以便能够看到详细的异常信息。

 

另外如果有对spring发布webservice以及spring+cxf发布webservice有问题的也可以找相关文章看一下。这里就不贴地址了。我也曾经介绍过cxf的。

 

总结一下,出错的缘由,由于本次webservice使用了手写xsd的方式,先手工写出了xsd,然后再生成的接口和对象,因此导致location设置不兼容。

 

 

 

参考:

unable-parse-url

nusoap-how-to-change-content-type-of-request

终于解决了PHP调用SOAP过程中的种种问题

 

分享一个xml在线格式化的工具:http://www.shell-tools.net/index.php?op=xml_format

文章转自http://phpxiaoxin.iteye.com/blog/1555715