SOAP: java+xfire(web service) + php客户端

作者: 吴俊杰

web service这项技术暂不说它有多落伍,但是项目中用到了,没法逃避!
    xml和json各有各的好处,但是JSON无疑是当今数据交互的主流了。客户soap服务器端用的是 java+xfire开发的,而我不懂java,可是我和客户的程序有数据交互,起初我推荐用json数据格式,但是客户执意要用web service。拗不过,只有研究 php的soap了。
    事实证明:这可不是一件容易的事情,web service虽号称 “跨平台与开发语言无关” ,实则是机关重重,陷阱遍布!网上的参考资料凌乱不堪,去糟存精也绝非易事,调通这个确实是走了不少弯路,在没有人指导的情况下,一直在网上search,一路之艰辛实属不易!下面就就记录一下php和java+xfire互为 web service服务器之间的一些细节,防止再次用到的时候,又走弯路,浪费时间。

1)普及知识php的soap类库
参考:http://www.cnblogs.com/chance1/archive/2009/04/08/1431949.html

php有两个扩展可以实现web service,一个是NuSoap,另一个是php官方的soap扩展,由于soap是官方的,所以我们这里以soap来实现web service.由于默认是没有打开soap扩展的,所以自己先看一下soap扩展有没有打开。

在soap编写web service的过程中主要用到了SoapClient,SoapServer,SoapFault三个类。

SoapClient类
这个类用来使用Web services。SoapClient类可以作为给定Web services的客户端。
它有两种操作形式:
* WSDL 模式

* Non-WSDL 模式

在WSDL模式中,构造器可以使用WSDL文件名作为参数,并从WSDL中提取服务所使用的信息。

non-WSDL模式中使用参数来传递要使用的信息。

SoapServer类
这个类可以用来提供Web services。与SoapClient类似,SoapServer也有两种操作模式:WSDL模式和non-WSDL模式。这两种模式的意义跟 SoapClient的两种模式一样。在WSDL模式中,服务实现了WSDL提供的接口;在non-WSDL模式中,参数被用来管理服务的行为。
在SoapServer类的众多方法中,有三个方法比较重要。它们是SoapServer::setClass(),SoapServer::addFunction()和SoapServer::handle()。 

下面给出实例
定义一个提供服务的php类,这个类所提供的函数就是web service对外提供的服务
(比较简单,例子略)

注意事项(非常重要)
php自带的soap扩展提供的类或构造函数是:SoapClient::SoapClient,详见《php5参考手册》 ;而nusoap提供的类库是:nusoap_client(有下划线) ,详见nusoap安装包源文件“lib/class.soapclient.php”文件。这里你会觉得很奇怪,为什么文件名不是带下划线"class.soap_client.php"?原来是这样的,nusoap为了兼容php自带的扩展soap的代码,在此类库文件最后面一行有如下代码:

if (!extension_loaded('soap')) {
class soapclient extends nusoap_client {
}
}
所以网上就出现了乱七八糟的 用第三方nusoap类库初始化soap客户端对象的例子,但是运行的时候程序都没有报错!例如:有的代码是 $client=new soapclient(); 有的是 $client = new nusoap_client(); 就是上面的原因!不过这里要说明的是,php扩展自带的soap类,已经能够很好的处理soap客户端调用,但是它的弊端就是如果它作为soap server要生成web service的WSDL,就显得无能为力了。而第三方的nusoap却恰好很容易生成WSDL。所以我这个项目就把两者都利用上了。【重要】为了不至于造成类库冲突,我还是修改了nusoap的 class.soapclient.php 文件的代码,把最后面的:
if (!extension_loaded('soap')) {
class soapclient extends nusoap_client {
}
}
删掉了。那么我打算这样,事实上也是尝试了很多方法都不行而被逼成这样的: php自带的soap扩展nusoap第三方类库必须在我的业务系统上共存兼容;我的业务系统(php代码实现)既是soap服务器端,也是soap客户端。我用nusoap作为我的soap server,因为客户的java机器要用web service调用我提供给它的函数,而且我要生成WDSL;而我的php脚本也要作为soap客户端调用客户java服务器上的函数的时候,不得不用php扩展自带soapclient这个类,至少我测试nusoap做soap client就是没法实现,soapclient这个自带的类库,也是侥幸可以调用

==============================================================================

2) 同种语言的 web service 服务器端和客户端的实现
使用php soap扩展 建立 soap server和用php soap扩展的soapclient调用soap server 提供的函数非常简单,不用举例了。同理如果用java实现soap也应该很容易,不过我不懂。

3)使用php作为soap client去调用java xfire做的web service
调用wsdl的web service,从《php5参考手册》上看到的例子:
第一:调用成功

SoapClient::SoapClient
<?php
## 第一类方法初始化soapclient

$client = new SoapClient("some.wsdl"); ##方法一
$client = new SoapClient("some.wsdl", array('soap_version' => SOAP_1_2)); ##方法一
$client = new SoapClient("some.wsdl", array('login' => "some_name", ##方法一
'password' => "some_password"));
$client = new SoapClient("some.wsdl", array('proxy_host' => "localhost", ##方法一
'proxy_port' => 8080));
$client = new SoapClient("some.wsdl", array('proxy_host' => "localhost", ##方法一
'proxy_port' => 8080,
'proxy_login' => "some_name",
'proxy_password' => "some_password"));
## 下面是第二类方法初始化soapclient
$client = new SoapClient(null, array('location' => "http://localhost/soap.php",  ##方法二,注意 不可以带上"?wsdl"
'uri' => "http://test-uri/"));
$client = new SoapClient(null, array('location' => "http://localhost/soap.php",
'uri' => "http://test-uri/",
'style' => SOAP_DOCUMENT,
'use' => SOAP_LITERAL));
?>

据我测试 ,方法一(只要是明确指定了.wsdl文档的)完全行不通,如果使用方法一,那么总是无法与web service交互函数参数!总是报错,例如代码为:

$SoapClient = new SoapClient("http://192.168.100.187:8080/GBMP/services/CTIService?wsdl");
var_dump( $SoapClient->setICTCompanyInfo("中文字符")) ;
那么运行后,报错如下:
Fatal error: Uncaught SoapFault exception: [soap:Client] Not enough message parts were received for the operation
如果把上面的 wsdl文件下载下来后保存在本地,发现也不行!我猜测这可能与java有关,尽管soap是跨平台的。

【重要】后来发现只有方法二才能实现,代码如下:

$arrOptions=array(
//注意: 这个location指定的是server端代码在服务器中的具体位置, 而且不能在最后加上"?wdsl"字符串
'location'=>'http://192.168.100.187:8080/GBMP/services/CTIService',
#'location'=>'http://192.168.100.187:8080/GBMP/services/CTIService?wsdl', ##错误
'uri'=>'http://192.168.100.187:8080/',
);
$SoapClient = new SoapClient(null,$arrOptions); //实例化客户端对象
var_dump( $SoapClient->setICTCompanyInfo("中文字符"));
或者用:
$SoapClient->__call('setICTCompanyInfo',array("中文字符"));  ##如果用__call的方法调用,那么参数必须用一维索引数组!
第二:多参数的传递方法
如果要传递多个参数,可以直接在后面加参数列表就行,例如:
$SoapClient->setICTCompanyInfo("中文字符",$arg2,$arg3,......) ##这里有个陷阱,就是参数个数必须严格和server端函数一样,不可多传,否则报错
或者:
$SoapClient->__call('setICTCompanyInfo',array("中文字符",$arg2,$arg3,......)) 


提示:至于上面的soapclient功能 如何用 第三方类库(nusoap)实现,尝试过几次,发现都失败!暂时没有时间继续测试下去...

3) 使用php第三方类库nusoap输出wsdl文档
成功的源代码如下:

<?php
require('/lib/nusoap.php');
$nusoap_server = new nusoap_server();
$nusoap_server->soap_defencoding = 'UTF-8';  ##必须指定编码,否则如果参数有中文,那么对端会乱码
$nusoap_server->decode_utf8 = false;  ##必须指定编码,否则如果参数有中文,那么对端会乱码
$nusoap_server->configureWSDL('wsdl_test'); ##这里随便写吧,暂不知道有何用
$nusoap_server->register('getBccd',array('user'=>'xsd:string','pass'=>'xsd:string'),array('return'=>'xsd:string')); //正确

function getBccd($user,$pass){
return "Succeed! $user $pass 服务器 is ok!";
}
$HTTP_RAW_POST_DATA = isset($HTTP_RAW_POST_DATA)?$HTTP_RAW_POST_DATA : '';
$nusoap_server->service($GLOBALS['HTTP_RAW_POST_DATA']);
?>

关于多参数的指定,在register()函数的第二个参数,必须是一个“关联数组”,上面“绿色”的对应关系,不多解释了!
其它:参考url:   http://blog.csdn.net/china_skag/article/details/7284227

$_POST:通过 HTTP POST 方法传递的变量组成的数组。是自动全局变量。
$GLOBALS['HTTP_RAW_POST_DATA'] :总是产生 $HTTP_RAW_POST_DATA 变量包含有原始的 POST 数据。此变量仅在碰到未识别 MIME 类型的数据时产生。$HTTP_RAW_POST_DATA 对于 enctype="multipart/form-data" 表单数据不可用。
也就是说基本上$GLOBALS['HTTP_RAW_POST_DATA'] 和 $_POST是一样的。
但是如果post过来的数据不是PHP能够识别的,你可以用 $GLOBALS['HTTP_RAW_POST_DATA']来接收,比如 text/xml 或者 soap 等等。
补充说明:PHP默认识别的数据类型是application/x-www.form-urlencoded标准的数据类型。
【好例子】

<?php
require_once("lib/nusoap.php");
$soap = new soap_server;
function savaNetData($moduleID,$pageArticleNumber,$webserverUrl){
$filename = 'config.txt';
$fp = fopen($filename, 'w+');

$content = "<config><moduleID>".$moduleID."</moduleID><number>".$pageArticleNumber
."</number><webserverUrl>".$webserverUrl."</webserverUrl></config>";
if ($fp) {
fwrite($fp, $content);
fclose($fp);
return 1;
} else {
die("Create File $filename error");
return -1;
}
}
$soap->configureWSDL("ServerPHPService",""); //初始化对WSDL的支持
// 注册服务
$soap->register('savaNetData',
array("moduleID"=>"xsd:int","pageArticleNumber"=>"xsd:int","webserverUrl"=>"xsd:string"), // 输入参数的定义
array("return"=>"xsd:int") // 返回值的定义
);
$HTTP_RAW_POST_DATA = isset($HTTP_RAW_POST_DATA)?$HTTP_RAW_POST_DATA : '';
$soap->service($HTTP_RAW_POST_DATA);
?>

其它参考例子:
http://space.yoka.com/blog/923799/4665589.html

原文地址:https://www.cnblogs.com/voiphudong/p/3580351.html