首先说明一下,这个小程序是我自己用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'])){ $ip[] = $_SERVER['REMOTE_ADDR']; } if(isset($_SERVER['HTTP_VIA'])){ $tmp = explode(', ',$_SERVER['HTTP_X_FORWARDED_FOR']); $ip = array_merge($ip,$tmp); } $ip = array_unique($ip); return $ip; } }
然后在服务端的入口文件中调用该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();
然后创建一个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'; } }
下面是客户端的主程序: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; } }
使用起来非常简单,下面是一个调用程序:
<?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'));
本文所涉及到的所有代码及主程序我都打包到下面的zip文件中,可以直接下载,有什么疑问可以直接在下面留言回复。
转载请著名出处,本文地址:http://hmw.iteye.com/blog/1322406