一個PHP寫的簡單webservice服務端+客戶端
首先說明一下,這個小程序是我自己用PHP寫成的一個簡單的webservice系統,包括服務端的程序和客戶端的程序,無論是服務端還是客戶端在使用起來都非常的簡單方便,也可以很方便的移植到自己的項目里,我自己也已經在稍微改造后用在了自己的項目里,應用到生產環境2個多月以來都很穩定,沒有出過什么問題。
這個簡單的webservice小程序有以下幾個優點:
1. 簡單、易用,幾乎沒有什么學習成本
2. 可擴展性很強,因為簡單,所以你可以在這個基礎上擴展出很多的東西,比如返回的數據格式上可以加上xml的支持等,這個就需要自己動手了
3. 數據傳輸量小,服務端到客戶端的數據傳輸采用gzip壓縮的方式,極大的減小了數據的體積,我自己做的測試是,一份4.7M的html數據在壓縮后只有113K
4. 有一定的安全性,首先服務端和客戶端之間的通訊會有密鑰機制,同時又采取限定IP的方式保護了接口的安全。
當然,也有缺點:比如程序過于簡單,沒有對安全性和數據過過多的校驗,這個在應用到生產環境之前一定要記得加強一下;客戶端到服務端的請求默認采用get形式,傳輸的數據量有限,這個我會考慮在以后的改進中改為post,同時數據也采用gzip壓縮以后傳輸。
好了,言歸正傳,下面介紹一下代碼本身:
首先是服務端,服務端有一個主要的class組成:apiServer.php
<?php /**
- apiServer.php *
- webservice主類 *
- @filename apiServer.php
- @version v1.0
- @update 2011-12-22
- @author homingway
- @contact homingway@gmail.com
@package webservice */ define('API_AUTH_KEY', 'i8XsJb$fJ!87FblnW'); class apiServer{
//請求參數 public $request = array();
//是否ip限制 public $ip_limit = true; //允許訪問的IP列表 public $ip_allow = array('127.0.0.1','192.168.0.99');
public $default_method = 'welcome.index'; public $service_method = array();
//私有靜態單例變量 private static $_instance = null;
/**
構造方法,處理請求參數 */ private function __construct(){ $this->dealRequest(); }
/**
單例運行 */ public static function getInstance(){ if(self::$_instance === null){
self::$_instance = new self();
} return self::$_instance; }
/**
運行 */ public function run(){ //授權 if(!$this->checkAuth()){
exit('3|Access Denied');
} $this->getApiMethod(); include_once(API_SERVICE_PATH.'/'.$this->service_method['service'].'.php'); $serviceObject = new $this->service_method['service']; if($this->request['param']){
$result = call_user_func_array(array($serviceObject,$this->service_method['method']),$this->request['param']);
} else {
$result = call_user_func(array($serviceObject,$this->service_method['method']));
} if(is_array($result)){
$result = json_encode($result);
} $result = gzencode($result); exit($result); }
/**
檢查授權 */ public function checkAuth(){ //檢查參數是否為空 if(!$this->request['time'] || !$this->request['method'] || !$this->request['auth']){
return false;
}
//檢查auth是否正確 $server_auth = md5(md5($this->request['time'].'|'.$this->request['method'].'|'.API_AUTH_KEY)); if($server_auth != $this->request['auth']){
return false;
}
//ip限制 if($this->ip_limit){
$remote_ip = $this->getIP(); $intersect = array_intersect($remote_ip,$this->ip_allow); if(empty($intersect)){ return false; }
}
return true; }
/**
獲取服務名和方法名 */ public function getApiMethod(){ if(strpos($this->request['method'], '.') === false){
$method = $this->default_method;
} else {
$method = $this->request['method'];
} $tmp = explode('.', $method); $this->service_method = array('service'=>$tmp[0],'method'=>$tmp[1]); return $this->service_method; }
/**
獲取和處理請求參數 */ public function dealRequest(){ $this->request['time'] = $this->_request('time'); $this->request['method'] = $this->_request('method'); $this->request['param'] = $this->_request('param'); $this->request['auth'] = $this->_request('auth'); if($this->request['param']){
$this->request['param'] = json_decode(urldecode($this->request['param']),true);
} }
/**
- 獲取request變量
@param string $item */ private function _request($item){ return isset($_REQUEST[$item]) ? trim($_REQUEST[$item]) : ''; }
/**
- 設置IP限制
@param bool $limit */ public function setIPLimit($limit=true){ $this->ip_limit = $limit; }
/**
- 獲取客戶端ip地址
*/
public function getIP(){
$ip = array();
if(isset($_SERVER['REMOTE_ADDR'])){
} if(isset($_SERVER['HTTP_VIA'])){$ip[] = $_SERVER['REMOTE_ADDR'];
} $ip = array_unique($ip); return $ip; }$tmp = explode(', ',$_SERVER['HTTP_X_FORWARDED_FOR']); $ip = array_merge($ip,$tmp);
}</pre>然后在服務端的入口文件中調用該class,并啟動服務即可,如:
<?php /**
- server.php *
- 自定義數據接口的入口 *
- @filename server.php
- @version v1.0
- @update 2011-12-22
- @author homingway
- @contact homingway@gmail.com
- @package webservice */
//API的根目錄 define('API_PATH',dirname(FILE));
//服務目錄 define('API_SERVICE_PATH',API_PATH.'/service'); define('API_LIB_PATH', API_PATH.'/lib');
//服務核心class include_once(API_LIB_PATH.'/apiServer.php');
//運行 apiServer::getInstance()->run();</pre>然后創建一個service的目錄,里面就是自己的接口class,如welcome.php:
<?php /**
- welcome.php *
- 功能代碼 *
- @filename welcome.php
- @version v1.0
- @update 2011-12-22
- @author homingway
- @contact homingway@gmail.com
- @package webservice */
class welcome{
public function index(){
return 'hello service';
}
}</pre>下面是客戶端的主程序:apiClient.php
<?php /**
- apiClient.php *
- webservice客戶端程序 *
- @filename apiClient.php
- @version v1.0
- @update 2011-12-22
- @author homingway
- @contact homingway@gmail.com
- @package webservice */
define('API_AUTH_KEY', 'i8XsJb$fJ!87FblnW');
class apiClient{
public static function send($url,$method,$param=array()){
$time = time();
$auth = md5(md5($time.'|'.$method.'|'.API_AUTH_KEY));
if(!is_array($param) || empty($param)){
$json_param = '';
} else {
$json_param = urlencode(json_encode($param));
}
$api_url = $url.'?method='.$method.'&time='.$time.'&auth='.$auth.'¶m='.$json_param;
$content = file_get_contents($api_url);
if(function_exists('gzdecode')){
$content = gzdecode($content);
} else {
$content = self::gzdecode($content);
}
return $content;
}
public static function gzdecode($data) {
$len = strlen ( $data );
if ($len < 18 || strcmp ( substr ( $data, 0, 2 ), "\x1f\x8b" )) {
return null; // Not GZIP format (See RFC 1952)
}
$method = ord ( substr ( $data, 2, 1 ) ); // Compression method
$flags = ord ( substr ( $data, 3, 1 ) ); // Flags
if ($flags & 31 != $flags) {
// Reserved bits are set -- NOT ALLOWED by RFC 1952
return null;
}
// NOTE: $mtime may be negative (PHP integer limitations)
$mtime = unpack ( "V", substr ( $data, 4, 4 ) );
$mtime = $mtime [1];
$xfl = substr ( $data, 8, 1 );
$os = substr ( $data, 8, 1 );
$headerlen = 10;
$extralen = 0;
$extra = "";
if ($flags & 4) {
// 2-byte length prefixed EXTRA data in header
if ($len - $headerlen - 2 < 8) {
return false; // Invalid format
}
$extralen = unpack ( "v", substr ( $data, 8, 2 ) );
$extralen = $extralen [1];
if ($len - $headerlen - 2 - $extralen < 8) {
return false; // Invalid format
}
$extra = substr ( $data, 10, $extralen );
$headerlen += 2 + $extralen;
}
$filenamelen = 0;
$filename = "";
if ($flags & 8) {
// C-style string file NAME data in header
if ($len - $headerlen - 1 < 8) {
return false; // Invalid format
}
$filenamelen = strpos ( substr ( $data, 8 + $extralen ), chr ( 0 ) );
if ($filenamelen === false || $len - $headerlen - $filenamelen - 1 < 8) {
return false; // Invalid format
}
$filename = substr ( $data, $headerlen, $filenamelen );
$headerlen += $filenamelen + 1;
}
$commentlen = 0;
$comment = "";
if ($flags & 16) {
// C-style string COMMENT data in header
if ($len - $headerlen - 1 < 8) {
return false; // Invalid format
}
$commentlen = strpos ( substr ( $data, 8 + $extralen + $filenamelen ), chr ( 0 ) );
if ($commentlen === false || $len - $headerlen - $commentlen - 1 < 8) {
return false; // Invalid header format
}
$comment = substr ( $data, $headerlen, $commentlen );
$headerlen += $commentlen + 1;
}
$headercrc = "";
if ($flags & 1) {
// 2-bytes (lowest order) of CRC32 on header present
if ($len - $headerlen - 2 < 8) {
return false; // Invalid format
}
$calccrc = crc32 ( substr ( $data, 0, $headerlen ) ) & 0xffff;
$headercrc = unpack ( "v", substr ( $data, $headerlen, 2 ) );
$headercrc = $headercrc [1];
if ($headercrc != $calccrc) {
return false; // Bad header CRC
}
$headerlen += 2;
}
// GZIP FOOTER - These be negative due to PHP's limitations
$datacrc = unpack ( "V", substr ( $data, - 8, 4 ) );
$datacrc = $datacrc [1];
$isize = unpack ( "V", substr ( $data, - 4 ) );
$isize = $isize [1];
// Perform the decompression:
$bodylen = $len - $headerlen - 8;
if ($bodylen < 1) {
// This should never happen - IMPLEMENTATION BUG!
return null;
}
$body = substr ( $data, $headerlen, $bodylen );
$data = "";
if ($bodylen > 0) {
switch ($method) {
case 8 :
// Currently the only supported compression method:
$data = gzinflate ( $body );
break;
default :
// Unknown compression method
return false;
}
} else {
// I'm not sure if zero-byte body content is allowed.
// Allow it for now... Do nothing...
}
// Verifiy decompressed size and CRC32:
// NOTE: This may fail with large data sizes depending on how
// PHP's integer limitations affect strlen() since $isize
// may be negative for large sizes.
if ($isize != strlen ( $data ) || crc32 ( $data ) != $datacrc) {
// Bad format! Length or CRC doesn't match!
return false;
}
return $data;
}
}</pre>使用起來非常簡單,下面是一個調用程序:
<?php /**
- demo.php *
- 客戶端調用示例 *
- @filename demo.php
- @version v1.0
- @update 2011-12-22
- @author homingway
- @contact homingway@gmail.com
- @package webservice */
include_once('../client/apiClient.php');
$server_uri = 'http://localhost/webservice/server/server.php';
print_r(apiClient::send($server_uri,'welcome.index'));</pre>轉載地址: http://hmw.iteye.com/blog/1322406