終於解決了PHP調用SOAP過程中的種種問題

最近在做公司和第三方的一個合作項目,需要調用統一驗證接口和統一支付接口。由於牽涉公司機密,所以我要單獨寫一層PHP的接口給第三方用。前面那個驗證接口主要卡在了des加密的方式上,這個有時間再說。這篇主要說說在實現統一支付接口上的問題。
 
  統一支付顧名思義,是公司的扣費系統,其中提供許多支付方式和支付種類(比如支付寶之類的),然後還會讓你選擇提交的銀行方式。這裏的邏輯業務我以後再說,主要說說這裏涉及的一個概念。由於在網上銀行交費頁面提交完數據後,頁面不會自動進行跳轉,所以底層的接口(我們這裏是JAVA實現的)要自動監聽返回狀態。但是返回的必然是一個加密的串,稱爲TokenString。這個TokenString的解密算法顯然只有公司內部的人才能夠知道,所以對於給合作方提供的時候,我必然要寫一個WEBSERVICE來提供JAVA程序異步調用來解密,然後前臺程序也來通過這個程序獲取返回狀態,比如交費是否成功之類的。好了,前面是我要做這東西的起因。下面具體說我在開發過程中遇到的問題吧。相信如果你用php要開發這個業務的話,都會遇到這個問題的。

-----------------------------------分割線-----------------------------------------------

   webservice的一種常用實現方式就是soap了。我們後端的JAVA也是用soap的原理實現的。那麼我顯然首先要上網上搜搜關於soap的文章。最早進入實現的是PHP寫的nusoap類。這個nusoap.php文件是完全用PHP寫法來實現的soap方法。優點是不用給php裝動態模塊,也不用重新編譯PHP。我當時像找到了新大陸一樣,一頭就載進去了。
先構建一個soap_server.php的文件,主要就是負責把給TokenString解密的getMsgSoap函數。具體解密算法我肯定是封裝在自己寫的類裏了。
<?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";


//創建soap服務類
$server = new soap_server;
$server->register('getMsgSoap');

//$pay = newIFengPay();   //創建支付類
$pay =IFengPay::getInstance();   //單態創建一個類
//$token =$pay->des->decrypt('C445FFDB6DE7093E58E0D60F9249B54DBEE2719EAD036262BCAABD5C234155B98229C4F8283B643FF2A454B22CED20F1C53C75F6C578EC30F597F656B125997CF0121F184989D32CFA1D40E74ECA4FBE93212FF5FC839625EE459294FF052A9FBC1F1961DBA8FAB6DBFB6E3C1A53FFBEA91B0C95E370588C44C9B1A5786A49D6BC67D79894A65664');   //調用des解密算法


function getMsgSoap($token)
{
    global$pay;
    $token =$pay->des->decrypt($token);   //調用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,實際上就是調用soap_server.php上封裝好的方法。
代碼如下:
<?php
//引用numsoap包。
require('./include/nusoap.php');

//soap的服務端
$server_url ='http://app.finance.ifeng.com/Sso/soap_server.php';

//創建一個實例
$client = new soapclient($server_url);
//調用的soap端的函數協議
$soap_method_name = 'getMsgSoap';
//TokenString是返回的加密串的參數名
$data = array('token' =>$_GET['TokenString']);
$result = $client->call($soap_method_name,$data);
// Display the result
print_r($result);
?>

   測試完了,我一測試,OK了。高興,興奮,原來這麼簡單。結果沒過多久,我們技術部的同事yetao就找我來了,你這個WEBSERVICE我那邊調用不了。
   其實我當時還沒有害怕,看看怎麼回事兒唄。結果yetao說我這個nusoap自動生成的wsdl和他JAVA,還有C#生成的wsdl不統一。也就是說我這個不是標準的。暈,不是吧。心裏涼了半截。
   這裏需要給大家說一下什麼是WSDL,基本上這就是XML的一種標準變形,類似於SVG和XSL那種,又自己的命名規則。剩下的知識大家搜索一下吧。

   沒辦法了。換別的方法吧。於是繼續在網上尋覓着。突然有篇帖子說nusoap已經過時了,而且通用性不好,最好還是用PHP5中自帶的soap函數。切,最後還是躲不過要安裝動態模塊的老路。結果就看看手冊上的安裝幫助唄。手冊上的安裝需求部分會提示Thisextension makes use of the »GNOME xml library. Download and install this library. You willneed at least libxml-2.5.4.於是我開始在網上找libxml的package,這個還挺好找了,用了和上次mcrypt一樣的安裝方法,詳見http://blog.sina.com.cn/s/blog_582246d20100dej9.html

   裝完了libxml我說繼續找soap的安裝包吧,結果滿互聯網找竟然都沒找到一個安裝的文件,無論是php.net,還是pecl網站都沒找到,也可能是我笨吧。問問合作方用的PHP是什麼版本,發現和我的一樣,我想同樣的版本說不定直接把soap.so文件要過來就成了(這絕對是用多了windows系統人的弊病,別以爲這是php_soap.dll那樣的,因爲只要是在windows環境下,dll文件就通用)但是在linux下可就不同了,果不其然,對方的系統是centos,和我這邊的不同。這條路也斷了。

   問了問技術部同事sunli,他建議我只能重新編譯php了,然後在編譯是加上--enable-soap。於是我寫個phpinfo()把ConfigureCommand 對應的代碼都拷貝下來,然後再加上我新要加上的這個模塊。
代碼如下:
'./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命令解壓,然後執行這編譯命令,再make&& makeinstall來安裝。完畢後重啓apache,再看phpinfo。發現soap包終於有了。看來有的時候想躲一些事情是躲不掉的。但是這樣有一個問題,就是現在這個只是在測試機上編譯安裝。如果在產品機上安裝,那肯定得找個瀏覽人數少的時候,所以未來這一兩天,我看那天晚上方便,給產品機重新編譯一下。

然後重新構造這兩個soap文件,準確的說是三個,因爲還要自己構造一個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
{
    publicfunction getMsgSoap($token = '')
    {
       $pay =IFengPay::getInstance();   //單態創建一個類
       $token =$pay->des->decrypt($token);   //調用des解密算法
       return$token;
    }
}
$server = new SoapServer('msg.wsdl', array('soap_version'=> SOAP_1_2,'encoding'=>'UTF-8'));  
$server->setClass("msg");  
$server->handle();  
?>

編碼強制轉化爲了UTF-8,然後聲明一個webservice類msg,這裏也可以聲明function的。

soap_client.php代碼如下:
<?php
//$client = newSoapClient('http://app.finance.ifeng.com/Sso/msg.wsdl');   
$client = newSoapClient("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 "Sorryan error was caught executing your request:{$e->getMessage()}";  
}  

print_r($result);
?>
   其中第一行註釋的就是wsdl的位置,因爲nusoap是自動生成wsdl的,所以我們之前一直沒有考慮這個東西。這個wsdl是可以通過zend自動生成的。當你寫好一個soap_server.php的時候,在zend中執行“工具”->“WSDL生成器”,然後選定一個wsdl文件,這個文件不同於JAVA和C#中,需要我們事先建立好這個文件,然後選中這個文件後點擊下一步,選擇綁定的文件(也就是我們這個soap_server.php),於是就會自動顯示這個文件中的方法了。選中你想填入到wsdl中的方法,然後complete就可以了。生成的這個wsdl是標準的可以被JAVA中soap方法識別的。

   最後就當我以爲已經成功的時候,程序上又輸出了一行錯誤。“Unable to parseURL”也就是$e->getMessage()這行生效了。我在百度上搜索數條誤解後,開始求助google了。於是在一篇英文的文章中找到了解決方法,不得否認,還是老外牛啊。
該文鏈接:http://www.electrictoolbox.com/php-soapclient-unable-parse-url/

根據這篇文章,我把大家需要改動的部分羅列出來吧,方便看英文不方便的同學。
1、首先你要在生成的wsdl文件中找到<soap:addresslocation=""/>,然後改爲<soap:addresslocation="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、最後就是網上關於Soap類調用的例子都是直接

$client = new SoapClient('http://host/path/file.php?wsdl');

但是後面還要加上一個參數的,改爲下面這句。

$client = new SoapClient('http://host/path/file.php?wsdl',
  array('cache_wsdl' => 0));

這三步都修改完畢後,重啓apache,再看你的頁面,發現終於成功了!OH,終於成功了!


在最後的最後,yetao說他那邊雖然能看見我這個方法了,但是一傳參數還是會報錯,由於時間緊,我們就改http的傳送方式來解決這個問題了。不過這個問題的源頭可能是這句話。在網上看到的。“特別注意:我發現調用phpwebserver的方法和調用.net web服務的方法不一樣。 調用.net service方法必須傳入命名參數;而調用phpweb服務方法,一定不能傳入命名參數,只能按順序傳入,爲什麼?這一點尤其要注意”。有時間再研究吧,這個已經佔用我很多時間了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章