近期使用PHP的SoapClient調用.net的WebServices始終行不通,然而又沒有對方技術人員支援的情況,嘗試着 WSDL 模式和 non-WSDL 模式都無果,在 new SoapClient時直接會報錯,在網上找了一個服務是可以正常調用的,說明運作環境正常:
$wsdlUrl = "http://www.webxml.com.cn/WebServices/IpAddressSearchWebService.asmx?wsdl";
$client = new SoapClient($wsdlUrl, $array('encoding' => 'utf-8'));
//$client = new SoapClient(null, array('encoding' => 'utf-8', 'uri'=> $uri, 'location' => $wsdlUrl, 'trace' => true));
但我這邊調用的服務是 HTTPS 帶登入驗證的服務,連執行個體化時都過不去,于是改用POST方式嘗試着。
對方給的 WebServices URL直接通路的是跳登入頁了,登入之後通路 WebServices 是正常的,則先使用cURL的模拟登入然後攜帶登入傳回的cookie請求WebServices,請求的參數中有一個是将XML轉為 base64Binary 傳輸。
傳輸的編碼 Content-Type:application/x-www-form-urlencoded
是以在這裡POST的參數為字元串,如 param1=value1¶m2=value2
如果使用數組,則 Content-Type 會變為表單上傳的類型。
以下用代碼和注釋說明:
<?php
/**
* CurlSoap
*
* 參考:
* https://github.com/Zjmainstay/php-curl
* https://segmentfault.com/a/1190000003808546
*/
class CurlSoap
{
/**
* @var string 服務位址,測試 https://localhost/Service.asmx/HelloWorld
*/
public $wsdl = 'https://localhost/Service.asmx/VisitorData';
/**
* @var string 驗證位址
*/
public $checkUrl = 'https://localhost/login.form';
/**
* @var string 如果失敗嘗試次數
*/
protected $testTry = 3;
protected $isCheck;
protected $cookieArr;
private $supplyCode;
private $username;
private $password;
public function __construct()
{
$this->cookieArr = array(array(),array());
$this->supplyCode = '';
$this->username = 'username';
$this->password = 'password';
//先檢測是否已登入
if(!$this->isCheck){
$this->checking();
}
}
/**
* 請求服務
*
*/
public function postdata()
{
try {
//将XML轉為 base64Binary , xml模闆由服務方提供
$xmlStr = $this->getXml();
//先将XML轉為 base64 string
$baseEncode = base64_encode($xmlStr);
//再轉為 byte 數組
$base64BinaryArr = $this->getBytes($baseEncode);
//将byte拼接為POST 的字元串參數時, 因對方不需要 &bytes[]= 可以接受到所有參數
$bytesParam = implode('&bytes=', $base64BinaryArr);
$param = array(
'url' => $this->wsdl,
'cookie' => $this->cookieArr[1], //攜帶登入後的cookie通路
'header' => array(
'Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Content-Type:application/x-www-form-urlencoded'
),
'data' => 'supplyCode='. $this->supplyCode .'&bytes='. $bytesParam
);
$res = $this->curlPost($param);
if($res){
$xmlObj = simplexml_load_string($res);
// print_r($xmlObj); exit;
if(strtoupper($xmlObj->CODE) == 'S'){
return true;
}
}
} catch (Exception $e) {
// libxml_get_last_error();
}
return false;
}
protected function getXml()
{
$xml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<msg ver="1.0">
<meta>
<field name="SOURCE" val="" />
</meta>
<head>
<field name="SEND" val="'. date('Y-m-d H:i:s') .'" type="System.DateTime" />
</head>
<body>
';
$xmlTpl = '<vo name="VISITOR">
<field name="ID" val="%d" type="System.Int32" />
<field name="NAME" val="%s" type="System.String" />
</vo>
';
$data = array(
array('id' => 1, 'user_name' => 'test'),
);
foreach ($data as $val){
$xml .= sprintf($xmlTpl, $val['id'], trim($val['user_name']) );
}
$xml .= '</body>
</msg>';
//格式化xml
$xml = preg_replace('/\s{2,}/', '', $xml);
$xml = str_replace('><', ">\n<", $xml);
return $xml;
}
/**
* 登入
*
*/
private function checking()
{
$param = array(
'url' => $this->checkUrl,
'nobody' => 1,
'header' => array(
'Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
'Content-Type:application/x-www-form-urlencoded'
),
'data' => 'username='. $this->username .'&password='. $this->password
);
//多次嘗試登陸
for($i=0; $i < $this->testTry; $i++){
$resData = $this->curlPost($param);
if(preg_match('@HTTP/1.1\s*200\s*[email protected]', $resData)){
$this->isCheck = true;
//登入成功 儲存Cookie
preg_match_all('|Set-Cookie: (.*);|U', $resData, $this->cookieArr);
break;
}
}
}
protected function curlPost($param)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $param['url']);
// 對認證證書來源的檢查, false 表示阻止對證書的合法性的檢查
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
// 從證書中檢查SSL加密算法是否存在
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);//關閉直接輸出
curl_setopt($ch,CURLOPT_POST,1);//使用post送出資料
curl_setopt($ch,CURLOPT_POSTFIELDS, $param['data']);//設定 post送出的資料, 差別數組與字元串
curl_setopt($ch,CURLOPT_USERAGENT,'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36');//設定使用者代理
//Boolean
if(isset($param['nobody'])){
//curl_setopt($ch, CURLOPT_NOBODY, 1); //不需要HTML實體
curl_setopt($ch, CURLOPT_HEADER, 1); //是否頭檔案資訊
}
/**
* Array
* array('Content-Type:application/x-www-form-urlencoded', 'Referer:https://localhost')
*/
if(isset($param['header'])){
curl_setopt($ch,CURLOPT_HTTPHEADER, $param['header']);//設定頭資訊
}
/**
* 攜帶Cookie通路
* array(0=>'SID=abcde', 1=>'ppid=123')
*/
if(isset($param['cookie'])){
$cookies = implode('; ', $param['cookie']);
curl_setopt($ch, CURLOPT_COOKIE, $cookies);
}
// curl_setopt($ch, CURLOPT_SSLVERSION, 3); //設定SSL版本
//儲存Cookie
//$cookie_file = '/tmp/rqwebservices.cookie'; //tempnam('/tmp','cookie');
// curl_setopt($ch,CURLOPT_COOKIEJAR,$cookie_file);//設定cookie的儲存目錄
$data = curl_exec($ch);
if (curl_errno($ch)) {
return false;
}
curl_close($ch);
return $data;
}
/**
* 将位元組數組轉化為String類型的資料
* @param array $bytes
* @return string
*/
public function toStr($bytes) {
$str = '';
foreach($bytes as $ch) {
$str .= chr($ch);
}
return $str;
}
/**
* 轉換一個String字元串為byte數組
* @param string $string
* @return array
*/
public function getBytes($string) {
$bytes = array();
for($i = 0; $i < strlen($string); $i++){
$bytes[] = ord($string[$i]);
}
return $bytes;
}
}