一个PHP写的简单webservice服务端+客户端

jopen 12年前
     首先说明一下,这个小程序是我自己用PHP写成的一个简单的webservice系统,包括服务端的程序和客户端的程序,无论是服务端还是客户端在使用起来都非常的简单方便,也可以很方便的移植到自己的项目里,我自己也已经在稍微改造后用在了自己的项目里,应用到生产环境2个多月以来都很稳定,没有出过什么问题。    <br />    <br /> 这个简单的webservice小程序有以下几个优点:    <br /> 1. 简单、易用,几乎没有什么学习成本    <br /> 2. 可扩展性很强,因为简单,所以你可以在这个基础上扩展出很多的东西,比如返回的数据格式上可以加上xml的支持等,这个就需要自己动手了    <br /> 3. 数据传输量小,服务端到客户端的数据传输采用gzip压缩的方式,极大的减小了数据的体积,我自己做的测试是,一份4.7M的html数据在压缩后只有113K    <br /> 4. 有一定的安全性,首先服务端和客户端之间的通讯会有密钥机制,同时又采取限定IP的方式保护了接口的安全。    <br />    <br /> 当然,也有缺点:比如程序过于简单,没有对安全性和数据过过多的校验,这个在应用到生产环境之前一定要记得加强一下;客户端到服务端的请求默认采用get形式,传输的数据量有限,这个我会考虑在以后的改进中改为post,同时数据也采用gzip压缩以后传输。    <br />    <br /> 好了,言归正传,下面介绍一下代码本身:    <br />    <br /> 首先是服务端,服务端有一个主要的class组成:apiServer.php    <pre class="brush:php; toolbar: true; auto-links: false;"><?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;  }  }</pre>然后在服务端的入口文件中调用该class,并启动服务即可,如:    <pre class="brush:php; toolbar: true; auto-links: false;"><?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:    <pre class="brush:php; toolbar: true; auto-links: false;"><?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    <pre class="brush:php; toolbar: true; auto-links: false;"><?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.'&param='.$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>使用起来非常简单,下面是一个调用程序:    <pre class="brush:php; toolbar: true; auto-links: false;"><?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>转载地址:    <a href="/misc/goto?guid=4959499860657997785" target="_blank">http://hmw.iteye.com/blog/1322406</a>