終于解決了PHP調(diào)用SOAP過程中的種種問題。
(2009-05-26 15:59:10)
最近在做公司和第三方的一個合作項(xiàng)目,需要調(diào)用統(tǒng)一驗(yàn)證接口和統(tǒng)一支付接口。由于牽涉公司機(jī)密,所以我要單獨(dú)寫一層PHP的接口給第三方用。前面那個驗(yàn)證接口主要卡在了des加密的方式上,這個有時間再說。這篇主要說說在實(shí)現(xiàn)統(tǒng)一支付接口上的問題。
統(tǒng)一支付顧名思義,是公司的扣費(fèi)系統(tǒng),其中提供許多支付方式和支付種類(比如支付寶之類的),然后還會讓你選擇提交的銀行方式。這里的邏輯業(yè)務(wù)我以后再說,主要說說這里涉及的一個概念。由于在網(wǎng)上銀行交費(fèi)頁面提交完數(shù)據(jù)后,頁面不會自動進(jìn)行跳轉(zhuǎn),所以底層的接口(我們這里是JAVA實(shí)現(xiàn)的)要自動監(jiān)聽返回狀態(tài)。但是返回的必然是一個加密的串,稱為TokenString。這個TokenString的解密算法顯然只有公司內(nèi)部的人才能夠知道,所以對于給合作方提供的時候,我必然要寫一個WEBSERVICE來提供JAVA程序異步調(diào)用來解密,然后前臺程序也來通過這個程序獲取返回狀態(tài),比如交費(fèi)是否成功之類的。好了,前面是我要做這東西的起因。下面具體說我在開發(fā)過程中遇到的問題吧。相信如果你用php要開發(fā)這個業(yè)務(wù)的話,都會遇到這個問題的。
-----------------------------------分割線-----------------------------------------------
webservice的一種常用實(shí)現(xiàn)方式就是soap了。我們后端的JAVA也是用soap的原理實(shí)現(xiàn)的。那么我顯然首先要上網(wǎng)上搜搜關(guān)于soap的文章。最早進(jìn)入實(shí)現(xiàn)的是PHP寫的nusoap類。這個nusoap.php文件是完全用PHP寫法來實(shí)現(xiàn)的soap方法。優(yōu)點(diǎn)是不用給php裝動態(tài)模塊,也不用重新編譯PHP。我當(dāng)時像找到了新大陸一樣,一頭就載進(jìn)去了。
先構(gòu)建一個soap_server.php的文件,主要就是負(fù)責(zé)把給TokenString解密的getMsgSoap函數(shù)。具體解密算法我肯定是封裝在自己寫的類里了。
<?php
define('IFENG_LOG_LEVEL_DEBUG', 8);
require('./include/nusoap.php');
include "./include/class.IFengSystem.php";
include "./include/class.IFengHttp.php";
include "./include/class.IFengPay.php";
//創(chuàng)建soap服務(wù)類
$server = new soap_server;
$server->register('getMsgSoap');
//$pay = new
IFengPay();
//創(chuàng)建支付類
$pay =
IFengPay::getInstance();
//單態(tài)創(chuàng)建一個類
//$token =
$pay->des->decrypt('C445FFDB6DE7093E58E0D60F9249B54DBEE2719EAD036262BCAABD5C234155B98229C4F8283B643FF2A454B22CED20F1C53C75F6C578EC30F597F656B125997CF0121F184989D32CFA1D40E74ECA4FBE93212FF5FC839625EE459294FF052A9FBC1F1961DBA8FAB6DBFB6E3C1A53FFBEA91B0C95E370588C44C9B1A5786A49D6BC67D79894A65664');
//調(diào)用des解密算法
function getMsgSoap($token)
{
global
$pay;
$token =
$pay->des->decrypt($token);
//調(diào)用des解密算法
return
$token;
}
$HTTP_RAW_POST_DATA = isset($HTTP_RAW_POST_DATA) ?
$HTTP_RAW_POST_DATA : '';
$server->service($HTTP_RAW_POST_DATA);
?>
然后再寫一個soap_client.php,實(shí)際上就是調(diào)用soap_server.php上封裝好的方法。
代碼如下:
<?php
//引用numsoap包。
require('./include/nusoap.php');
//soap的服務(wù)端
$server_url =
'http://app.finance.ifeng.com/Sso/soap_server.php';
//創(chuàng)建一個實(shí)例
$client = new soapclient($server_url);
//調(diào)用的soap端的函數(shù)協(xié)議
$soap_method_name = 'getMsgSoap';
//TokenString是返回的加密串的參數(shù)名
$data = array('token' =>
$_GET['TokenString']);
$result = $client->call($soap_method_name,
$data);
// Display the result
print_r($result);
?>
測試完了,我一測試,OK了。高興,興奮,原來這么簡單。結(jié)果沒過多久,我們技術(shù)部的同事yetao就找我來了,你這個WEBSERVICE我那邊調(diào)用不了。
其實(shí)我當(dāng)時還沒有害怕,看看怎么回事兒唄。結(jié)果yetao說我這個nusoap自動生成的wsdl和他JAVA,還有C#生成的wsdl不統(tǒng)一。也就是說我這個不是標(biāo)準(zhǔn)的。暈,不是吧。心里涼了半截。
這里需要給大家說一下什么是WSDL,基本上這就是XML的一種標(biāo)準(zhǔn)變形,類似于SVG和XSL那種,又自己的命名規(guī)則。剩下的知識大家搜索一下吧。
沒辦法了。換別的方法吧。于是繼續(xù)在網(wǎng)上尋覓著。突然有篇帖子說nusoap已經(jīng)過時了,而且通用性不好,最好還是用PHP5中自帶的soap函數(shù)。切,最后還是躲不過要安裝動態(tài)模塊的老路。結(jié)果就看看手冊上的安裝幫助唄。手冊上的安裝需求部分會提示This
extension makes use of the
GNOME xml library. Download and install this library. You will
need at least libxml-2.5.4.
于是我開始在網(wǎng)上找libxml的package,這個還挺好找了,用了和上次mcrypt一樣的安裝方法,詳見http://blog.sina.com.cn/s/blog_582246d20100dej9.html
裝完了libxml我說繼續(xù)找soap的安裝包吧,結(jié)果滿互聯(lián)網(wǎng)找竟然都沒找到一個安裝的文件,無論是php.net,還是pecl網(wǎng)站都沒找到,也可能是我笨吧。問問合作方用的PHP是什么版本,發(fā)現(xiàn)和我的一樣,我想同樣的版本說不定直接把soap.so文件要過來就成了(這絕對是用多了windows系統(tǒng)人的弊病,別以為這是php_soap.dll那樣的,因?yàn)橹灰窃趙indows環(huán)境下,dll文件就通用)但是在linux下可就不同了,果不其然,對方的系統(tǒng)是centos,和我這邊的不同。這條路也斷了。
問了問技術(shù)部同事sunli,他建議我只能重新編譯php了,然后在編譯是加上--enable-soap。于是我寫個phpinfo()把Configure
Command 對應(yīng)的代碼都拷貝下來,然后再加上我新要加上的這個模塊。
代碼如下:
'./configure' '--prefix=/usr/local/php5'
'--with-libxml-dir=/usr/lib' '--with-zlib'
'--with-gd=/usr/local/gdlibforphp/gd' '--with-zlib-dir=/usr'
'--with-mysql=/data/mysql' '--enable-sockets' '--enable-mbstring'
'--enable-soap' '--with-apxs2=/data/apache/bin/apxs'
'--enable-safe-mode' '--enable-ftp' '--with-png-dir=/usr/local'
'--with-freetype-dir=/usr'
'--with-jpeg-dir=/usr/local/gdlibforphp/jpeg'
'--with-sqlite=shared'
記住,編譯之前要把那個生成的文件夾刪除,從新用tar命令解壓,然后執(zhí)行這編譯命令,再make
&& make
install來安裝。完畢后重啟apache,再看phpinfo。發(fā)現(xiàn)soap包終于有了??磥碛械臅r候想躲一些事情是躲不掉的。但是這樣有一個問題,就是現(xiàn)在這個只是在測試機(jī)上編譯安裝。如果在產(chǎn)品機(jī)上安裝,那肯定得找個瀏覽人數(shù)少的時候,所以未來這一兩天,我看那天晚上方便,給產(chǎn)品機(jī)重新編譯一下。
然后重新構(gòu)造這兩個soap文件,準(zhǔn)確的說是三個,因?yàn)檫€要自己構(gòu)造一個wsdl文件。
soap_server.php程序代碼如下:
<?php
define('IFENG_LOG_LEVEL_DEBUG', 8);
include "./include/class.IFengSystem.php";
include "./include/class.IFengHttp.php";
include "./include/class.IFengPay.php";
class msg
{
public
function getMsgSoap($token = '')
{
$pay =
IFengPay::getInstance();
//單態(tài)創(chuàng)建一個類
$token =
$pay->des->decrypt($token);
//調(diào)用des解密算法
return
$token;
}
}
$server = new SoapServer('msg.wsdl', array('soap_version'
=> SOAP_1_2,
'encoding'=>'UTF-8'));
$server->setClass("msg");
$server->handle();
?>
編碼強(qiáng)制轉(zhuǎn)化為了UTF-8,然后聲明一個webservice類msg,這里也可以聲明function的。
soap_client.php代碼如下:
<?php
//$client = new
SoapClient('http://app.finance.ifeng.com/Sso/msg.wsdl');
$client = new
SoapClient("http://app.finance.ifeng.com/Sso/soap_server.php?WSDL",array('cache_wsdl'
=> 0));
$TokenString = $_GET['TokenString'];
try {
$result =
$client->getMsgSoap($TokenString);
} catch(SoapFault $e) {
print "Sorry
an error was caught executing your request:
{$e->getMessage()}";
}
print_r($result);
?>
其中第一行注釋的就是wsdl的位置,因?yàn)閚usoap是自動生成wsdl的,所以我們之前一直沒有考慮這個東西。這個wsdl是可以通過zend自動生成的。當(dāng)你寫好一個soap_server.php的時候,在zend中執(zhí)行“工具”->“WSDL生成器”,然后選定一個wsdl文件,這個文件不同于JAVA和C#中,需要我們事先建立好這個文件,然后選中這個文件后點(diǎn)擊下一步,選擇綁定的文件(也就是我們這個soap_server.php),于是就會自動顯示這個文件中的方法了。選中你想填入到wsdl中的方法,然后complete就可以了。生成的這個wsdl是標(biāo)準(zhǔn)的可以被JAVA中soap方法識別的。
最后就當(dāng)我以為已經(jīng)成功的時候,程序上又輸出了一行錯誤?!癠nable to parse
URL”也就是$e->getMessage()這行生效了。我在百度上搜索數(shù)條誤解后,開始求助google了。于是在一篇英文的文章中找到了解決方法,不得否認(rèn),還是老外牛啊。
該文鏈接:http://www./php-soapclient-unable-parse-url/
根據(jù)這篇文章,我把大家需要改動的部分羅列出來吧,方便看英文不方便的同學(xué)。
1、首先你要在生成的wsdl文件中找到<soap:address
location=""/>,然后改為<soap:address
location="http://app.finance.ifeng.com/Sso/soap_server.php"/>也就是換成你那個soap_server.php所在的位置。
2、修改php.ini,加入下面這幾行,是控制WSDL的緩存的。
[soap]
; Enables or disables WSDL caching feature.
soap.wsdl_cache_enabled=1
; Sets the directory name where SOAP extension will put cache files.
soap.wsdl_cache_dir="/tmp"
; (time to live) Sets the number of second while cached file will be used
; instead of original one.
soap.wsdl_cache_ttl=86400
3、最后就是網(wǎng)上關(guān)于Soap類調(diào)用的例子都是直接
$client = new SoapClient('http://host/path/file.php?wsdl');
但是后面還要加上一個參數(shù)的,改為下面這句。
$client = new SoapClient('http://host/path/file.php?wsdl',
array('cache_wsdl' => 0));
這三步都修改完畢后,重啟apache,再看你的頁面,發(fā)現(xiàn)終于成功了!OH,終于成功了!
在最后的最后,yetao說他那邊雖然能看見我這個方法了,但是一傳參數(shù)還是會報(bào)錯,由于時間緊,我們就改http的傳送方式來解決這個問題了。不過這個問題的源頭可能是這句話。在網(wǎng)上看到的。“特別注意:我發(fā)現(xiàn)調(diào)用php
webserver的方法和調(diào)用.net web服務(wù)的方法不一樣。 調(diào)用.net service方法必須傳入命名參數(shù);而調(diào)用php
web服務(wù)方法,一定不能傳入命名參數(shù),只能按順序傳入,為什么?這一點(diǎn)尤其要注意
”。有時間再研究吧,這個已經(jīng)占用我很多時間了。
|