通过nusoap学习soap知识的一些体会
最近一个短信项目中涉及到跨平台数据转发和调用的问题,因此就牵涉到以前未接触过的一个领域soap.好在有度娘和谷歌,好歹还是拿下了,过程中也涉及到多次失败和成功的经验,特记录在案,以备查询.
首先要解决跨平台通讯问题,应该有好多种技术手段,而soap估计算是有些过时了.目前较新的技术概念应该是REST,可惜我也欠学.加上第三方技术也只提供了http接口和webservice接口,恰好,以前学c#的时候对webservice多少有点印象,因此使用soap搭建webservice服务或者使用soap调用上游webservice就成为了首选方案(之所以同时要使用webservice和client是因为牵涉到数据转发,我需要自己搭建个server端给下游产品使用,同时把数据通过soap提交转发到上游).
php语言上soap还真不少,php5本身已经提供了soap扩展,还有其他第三方类库,选来选去选nusoap,虽然有点过时,不过因此不需要使用其他扩展,无需wsdl支持,因而对我来说非常方便,至少避免了下游产品安装在不同虚拟主机上的soap扩展权限的问题.
nusoap目前最新版本是0.9.5,下载下来的包里有多个类库文件,不过项目中只需要一个nusoap.php即可,此文件时间上包含了其他的文件中的所有内容.
使用nusoap首先有点需要注意的,如果您的php环境本身已经启用自带的soap扩展,那么需要做点小小改动,首先是nusoap.php文件中4582到4583行,
其次 文件末尾这两行
也注释掉,此两处代码支持使用以前的版本new soap_server()和new soap_client()方法向前兼容的,不过这两个方法和php内置的soap扩展方法可能会有冲突,因此注释掉避免更多问题.当然,具体的实例代码中,就使用nusoap原生的nusoap_server()方法以及nusoap_client方法来取代new soap_server()和new soap_client()方法.
//注:0.9.5版已经添加了!extension_loaded('soap')检查,如果php的soap扩展已经加载,是无法使用soap_server 和soap_clent方法的
首先要解决跨平台通讯问题,应该有好多种技术手段,而soap估计算是有些过时了.目前较新的技术概念应该是REST,可惜我也欠学.加上第三方技术也只提供了http接口和webservice接口,恰好,以前学c#的时候对webservice多少有点印象,因此使用soap搭建webservice服务或者使用soap调用上游webservice就成为了首选方案(之所以同时要使用webservice和client是因为牵涉到数据转发,我需要自己搭建个server端给下游产品使用,同时把数据通过soap提交转发到上游).
php语言上soap还真不少,php5本身已经提供了soap扩展,还有其他第三方类库,选来选去选nusoap,虽然有点过时,不过因此不需要使用其他扩展,无需wsdl支持,因而对我来说非常方便,至少避免了下游产品安装在不同虚拟主机上的soap扩展权限的问题.
nusoap目前最新版本是0.9.5,下载下来的包里有多个类库文件,不过项目中只需要一个nusoap.php即可,此文件时间上包含了其他的文件中的所有内容.
使用nusoap首先有点需要注意的,如果您的php环境本身已经启用自带的soap扩展,那么需要做点小小改动,首先是nusoap.php文件中4582到4583行,
class soap_server extends nusoap_server {
}
这里把这两行注释掉其次 文件末尾这两行
class soapclient extends nusoap_client {
}也注释掉,此两处代码支持使用以前的版本new soap_server()和new soap_client()方法向前兼容的,不过这两个方法和php内置的soap扩展方法可能会有冲突,因此注释掉避免更多问题.当然,具体的实例代码中,就使用nusoap原生的nusoap_server()方法以及nusoap_client方法来取代new soap_server()和new soap_client()方法.
//注:0.9.5版已经添加了!extension_loaded('soap')检查,如果php的soap扩展已经加载,是无法使用soap_server 和soap_clent方法的
然后就是nusoap服务端的搭建了:
代码如下 :
require_once ("../lib/nusoap/nusoap.php") ;//引入nusoap类库
$server = new nusoap_server () ;
// 避免乱码
$server->soap_defencoding = 'UTF-8' ;//使用utf8编码
$server->decode_utf8 = false ;//此处避免中文乱码问题,我实际使用中未发现过,不过根据其他网友说法必须设置
$server->xml_encoding = 'UTF-8' ;//使用utf8编码xml文件
//$server->configureWSDL ( 'sms_wsdl' ) ; // 打开 wsdl 支持,访问此webservice地址会看到标准的wsdl文件说明而无需生成wsdl文件
$server->configureWSDL('AuthorityServicewsdl', 'urn:AuthorityServicewsdl');//指定命名空间,实际上一般情况不指定使用也正常
$server->wsdl->schemaTargetNamespace = 'urn:AuthorityServicewsdl';//指定命名空间,
这两行代码网上资料较少,实际使用中我未发现相应作用.其概念是属于xml schemas里面的内容,具体的大家有兴趣去百度xml的相关定义.我也不甚了了
$server->register ( 'getInfo', array("channel_id" => "xsd:string","channel_key" => "xsd:string"),array ("return" => "xsd:string"));
吃完饭回来继续.....这两行代码网上资料较少,实际使用中我未发现相应作用.其概念是属于xml schemas里面的内容,具体的大家有兴趣去百度xml的相关定义.我也不甚了了
$server->register ( 'getInfo', array("channel_id" => "xsd:string","channel_key" => "xsd:string"),array ("return" => "xsd:string"));
$HTTP_RAW_POST_DATA = isset ( $HTTP_RAW_POST_DATA ) ? $HTTP_RAW_POST_DATA : '' ;
$server->service ( $HTTP_RAW_POST_DATA ) ;//service 处理客户端输入的数据
注意,这几行代码有几个不好理解的点首先是 sendSms这地方,这地方是注册一个webservice方法名称叫做sendSms,然后客户端就可以使用这个方法调用服务后文再讲.
其次 array("channel_id" => "xsd:string","channel_key" => "xsd:string")这个数组实际上定义了这个sendSms应该使用的参数数据类型,soap有几个基本类型xsd:string对应string,xsd:int对应int,xsd:boolean对应boolean,xsd:float对应float类型.
根据翻看nusoap代码,0.9.5版似乎支持object类型,但是实际调用中好像测试未成功,未做其他测试,
未完,待续
注意,这几行代码有几个不好理解的点首先是 sendSms这地方,这地方是注册一个webservice方法名称叫做sendSms,然后客户端就可以使用这个方法调用服务后文再讲.
其次 array("channel_id" => "xsd:string","channel_key" => "xsd:string")这个数组实际上定义了这个sendSms应该使用的参数数据类型,soap有几个基本类型xsd:string对应string,xsd:int对应int,xsd:boolean对应boolean,xsd:float对应float类型.
根据翻看nusoap代码,0.9.5版似乎支持object类型,但是实际调用中好像测试未成功,未做其他测试,
未完,待续
array ("return" => "xsd:string")这行代码是定义返回数据类型,支持nusoap的各种基本数据类型int,string 等,但是无法返回array.另据网上帖子有支持自定义符合数据类型的,但是我实际测试,死活无法成功,暂且搁下不说.我 暂时仅使用string返回复杂字符串,客户端再自行处理.
关于这地方的参数类型和返回类型,我在实际使用中浪费了大量的精力和并多次失败经验.其原因是自己不了解webservice的本质.当时看到一个上游厂商给的webservice接口文档,对方大约是使用java和c#开发的接口,文档中充满了大量的类,和对象的数据类型描述.例如:
getAccountBlacne(Account Accountinfo)方法,参数Account,实体类型,字段:name,string,password string;
看上去似乎是要构建一个实体类型Account才行.但是php这种弱语言中仅有stdClass类型似乎接近需求,我就傻乎乎的的构建了一个stdClass数据类包:
$account=new stdClass();
$account->name="test";
$account->password ="123";
可想而知,似乎也无法通过.
说到这里,需要说明的就是webservice 的原理了,其定义网上大把,有一个重要的要点,基于soap的webservice无论是服务的返回的数据还是客户端提交的数据,本质上都是一串xml格式的信息.例如某个天气预报的接口其中一个方法的请求和返回数据类包说明
POST /WebServices/WeatherWS.asmx HTTP/1.1Host: www.webxml.com.cnContent-Type: text/xml; charset=utf-8Content-Length: lengthSOAPAction: "http://WebXml.com.cn/getRegionCountry"<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <getRegionCountry xmlns="http://WebXml.com.cn/" /> </soap:Body> </soap:Envelope>
HTTP/1.1 200 OKContent-Type: text/xml; charset=utf-8Content-Length: length<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <getRegionCountryResponse xmlns="http://WebXml.com.cn/"> <getRegionCountryResult> <string>string</string> <string>string</string> </getRegionCountryResult> </getRegionCountryResponse> </soap:Body> </soap:Envelope>
soap_client客户端本质上是要构建这一个xml格式的请求数据包而已.由于xml的特性,发送出去的数据实际上都是字符串,服务端亦如是,因此我们不用管他们文档写的是啥类型的参数,只要照着xml的要求写就行了.例如以上这个服务端方法要求的参数 <string>string</string> <string>string</string>就是两串字符,客户端按顺序传进去就行
//此处个人理解不知是否准确,仅供参考
嗯,为了保证理解的顺序,客户端稍候再说.再讲讲服务端的代码:
$HTTP_RAW_POST_DATA = isset ( $HTTP_RAW_POST_DATA ) ? $HTTP_RAW_POST_DATA : '' ;
$server->service ( $HTTP_RAW_POST_DATA ) ;//service 处理客户端输入的数据
这两行是取得客户端请求数据并处理, HTTP_RAW_POST_DATA其实就类似于php中的$_POST,但是由于soap中传输的是xml数据,php是无法识别为post的,所以要用HTTP_RAW_POST_DATA,其定义可以百度.
$server->service ( $HTTP_RAW_POST_DATA ) 是nusoap的一个方法,主要是解析xml请求,返回相应数据,以及wsdl描述等等,我们就理解为webservice启动服务就好了.
好了,我们接下来终于接触client了
nusoap的客户语法更简单
require_once ("../lib/nusoap/nusoap.php");//引入nusoap类库
这两行是取得客户端请求数据并处理, HTTP_RAW_POST_DATA其实就类似于php中的$_POST,但是由于soap中传输的是xml数据,php是无法识别为post的,所以要用HTTP_RAW_POST_DATA,其定义可以百度.
$server->service ( $HTTP_RAW_POST_DATA ) 是nusoap的一个方法,主要是解析xml请求,返回相应数据,以及wsdl描述等等,我们就理解为webservice启动服务就好了.
好了,我们接下来终于接触client了
nusoap的客户语法更简单
require_once ("../lib/nusoap/nusoap.php");//引入nusoap类库
$client = new nusoap_client ( 'http://www.domain.com/webservice?wsdl' );
$client->soap_defencoding = 'UTF-8';
$client->http_encoding='utf-8';
$client->decode_utf8 = false;
$client->xml_encoding = 'UTF-8';
这里这几行跟服务端类似,就不讲述了,比较重要的是$client = new nusoap_client ( 'http://www.domain.com/webservice?wsdl' );这一行
这一行定义nusoap客户端,他一般有2个参数 new nusoap_client(path,wsdl),前面一个path参数就是你要调用的webservice的地址,有些资料说这个地址后面必须写成?wsdl例如http://www.xx.com/apiwebservice?wsdl,不过我本地搭建的webservice好像写不写都可以,为了安全起见还是写吧.
后面一个参数wsdl是是否启用wsdl文件,比如我本地webservice不需要wsdl文件的,这个参数就可以忽略,如果上游厂商给的接口是使用wsdl文件的,就写成$client = new nusoap_client ( 'http://www.domain.com/webservice?wsdl',true );或者$client = new nusoap_client ( 'http://www.domain.com/webservice?wsdl','wsdl' );
嗯,上面这些事定义nusoap客户端参数,下面就是具体的调用方法了
$paras = array('channel_id'=> 'test,'channel_key'=>"123");
$result = $client->call ( 'getInfo', $paras );
这里是重点,要结合前面的一行代码看
$server->register ( 'getInfo', array("channel_id" => "xsd:string","channel_key" => "xsd:string"),array ("return" => "xsd:string"));
注意,这里的红色的getInfo就是服务端提供的处理方法,客户端调用了这一个方法并获得返回数据
这里$paras变量里面提供了一个数组,其数组元素数量必须和服务端getInfo的预定义参数数量相同,此处都是2个
参数一下标名称是channel_id,对应着预定义的参数一 "channel_id" => "xsd:string"中的蓝色下标字符串,channel_key就对应着,"channel_key" => "xsd:string"中的channel_key,参数的下标和预定义的必须相同,否则会出错
.嗯,讲了半天,这个getInfo方法还没定义呢,不过这个就简单了,在服务端文件直接定义一个方法
function getInfo($channel_id,$channel_key)
{
$data='hellow' //处理方法
return $data;
}
这里需要注意,不能在方法中输出字符,例如echo或者print等,只能使用return返回数据,否则会报错,另外参数顺序要和array("channel_id" => "xsd:string","channel_key" => "xsd:string"),array ("return" => "xsd:string")定义的顺序一致,否则可能得不到预期效果.
嗯,基本的应用就这些了,想想还有啥没说的,哦,对了,还有一点东西,正式的程序一般不用,开发的使用用来调试的:
nusoap客户提供了2个方法$client->request和$client->response,这两个方法分别显示客户请求的实际数据包内容和服务的返回的数据包内容,都是xml格式的数据,如果两者的对应关键节点一致,一般来说程序就正常,如果有异常,就检查哪个节点不对应,通常能解决大部分问题
比如使用echo $client->request会显示类似下文:
<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns8870:hellow xmlns:ns8870="http://tempuri.org"><name xsi:type="xsd:string">zz</name></ns8870:hellow></SOAP-ENV:Body></SOAP-ENV:Envelope>
其中大部分内容都是soap封装好的,我们自己的客户端实现的就是提交一个请求方法和参数,即红色部分,如果服务端要求的数据是复杂结构的如多维数组,本地参数则构造对应的数组即可.
例如服务端可能如下:
<Post xmlns="http://www.xxx.com">
<account>string</account>
<password>string</password>
<dataPack>
<Type>int</Type>
<msgs>
<info>string</info>
<Content>string</Content>
</msgs>
</dataPack>
</Post>
那么我们本地需要构建如下数组作为参数
$a=array(
' account'=>'name',
'password'=>'pass',
'dataPack'=>array(
'Type'=>'type',
'msgs'=>array(
'info'=>'infoxxxx',
'Content'=>'内容'
)
)
);
$result = $client->call ( 'getInfo', $a);
$client->response显示数据和request类似,不再详述.
这里这几行跟服务端类似,就不讲述了,比较重要的是$client = new nusoap_client ( 'http://www.domain.com/webservice?wsdl' );这一行
这一行定义nusoap客户端,他一般有2个参数 new nusoap_client(path,wsdl),前面一个path参数就是你要调用的webservice的地址,有些资料说这个地址后面必须写成?wsdl例如http://www.xx.com/apiwebservice?wsdl,不过我本地搭建的webservice好像写不写都可以,为了安全起见还是写吧.
后面一个参数wsdl是是否启用wsdl文件,比如我本地webservice不需要wsdl文件的,这个参数就可以忽略,如果上游厂商给的接口是使用wsdl文件的,就写成$client = new nusoap_client ( 'http://www.domain.com/webservice?wsdl',true );或者$client = new nusoap_client ( 'http://www.domain.com/webservice?wsdl','wsdl' );
嗯,上面这些事定义nusoap客户端参数,下面就是具体的调用方法了
$paras = array('channel_id'=> 'test,'channel_key'=>"123");
$result = $client->call ( 'getInfo', $paras );
这里是重点,要结合前面的一行代码看
$server->register ( 'getInfo', array("channel_id" => "xsd:string","channel_key" => "xsd:string"),array ("return" => "xsd:string"));
注意,这里的红色的getInfo就是服务端提供的处理方法,客户端调用了这一个方法并获得返回数据
这里$paras变量里面提供了一个数组,其数组元素数量必须和服务端getInfo的预定义参数数量相同,此处都是2个
参数一下标名称是channel_id,对应着预定义的参数一 "channel_id" => "xsd:string"中的蓝色下标字符串,channel_key就对应着,"channel_key" => "xsd:string"中的channel_key,参数的下标和预定义的必须相同,否则会出错
.嗯,讲了半天,这个getInfo方法还没定义呢,不过这个就简单了,在服务端文件直接定义一个方法
function getInfo($channel_id,$channel_key)
{
$data='hellow' //处理方法
return $data;
}
这里需要注意,不能在方法中输出字符,例如echo或者print等,只能使用return返回数据,否则会报错,另外参数顺序要和array("channel_id" => "xsd:string","channel_key" => "xsd:string"),array ("return" => "xsd:string")定义的顺序一致,否则可能得不到预期效果.
嗯,基本的应用就这些了,想想还有啥没说的,哦,对了,还有一点东西,正式的程序一般不用,开发的使用用来调试的:
nusoap客户提供了2个方法$client->request和$client->response,这两个方法分别显示客户请求的实际数据包内容和服务的返回的数据包内容,都是xml格式的数据,如果两者的对应关键节点一致,一般来说程序就正常,如果有异常,就检查哪个节点不对应,通常能解决大部分问题
比如使用echo $client->request会显示类似下文:
<?xml version="1.0" encoding="UTF-8"?><SOAP-ENV:Envelope SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns8870:hellow xmlns:ns8870="http://tempuri.org"><name xsi:type="xsd:string">zz</name></ns8870:hellow></SOAP-ENV:Body></SOAP-ENV:Envelope>
其中大部分内容都是soap封装好的,我们自己的客户端实现的就是提交一个请求方法和参数,即红色部分,如果服务端要求的数据是复杂结构的如多维数组,本地参数则构造对应的数组即可.
例如服务端可能如下:
<Post xmlns="http://www.xxx.com">
<account>string</account>
<password>string</password>
<dataPack>
<Type>int</Type>
<msgs>
<info>string</info>
<Content>string</Content>
</msgs>
</dataPack>
</Post>
那么我们本地需要构建如下数组作为参数
$a=array(
' account'=>'name',
'password'=>'pass',
'dataPack'=>array(
'Type'=>'type',
'msgs'=>array(
'info'=>'infoxxxx',
'Content'=>'内容'
)
)
);
$result = $client->call ( 'getInfo', $a);
$client->response显示数据和request类似,不再详述.