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