1. 程式人生 > >簡易http介面實現

簡易http介面實現

        http介面是什麼,我一直沒找到比較明確的定義(如果有哪位可以提點一下,我將非常感謝),今天就自己來總結一下吧。個人認為,http介面就是一種基於http服務的api,是系統之間互動的一種約定,所謂的web service其實也就是一種http介面,只不過它是比較規範的、通用的。

        http介面有什麼用,個人體會是,http介面最主要的作用是能夠較好地解決不同系統(可能是功能不同、開發語言不同、服務商不同,等等,但都是基於http服務的)之間的互動的需求,比如像微信公眾號的各種介面,不管是使用Java開發還是PHP進行開發,不管是A公司在開發還是B公司在開發,也不管是要做商城系統還是做企業宣傳網站,都一樣可以很好地呼叫。

        接下來就展現一套自己寫的簡易的http介面,作為自己這段時間學習與實踐的一個小總結。這套介面的基本框架,在本人的具體開發實踐中是有用到的,目前來看,執行效率和可擴充套件性還算能滿足需要。

        下圖就是整個框架專案的目錄結構

        整個介面服務端的處理流程如下:

        1、接收並解析請求;

        包括檢查資料格式、驗證簽名等。

        2、根據請求例項化對應的處理器;

        簡單工廠模式,對映規則為:介面名稱為test,則例項化apis\TestApi類。

        3、處理器具體處理請求;

        呼叫處理器的handle()方法,比如userinfo介面,handle()可能就是去資料庫中查詢響應的user資訊。

        4、處理器輸出響應

        呼叫response()方法。

        前面也說了,介面是一種約定,因此傳送資料和輸出響應都是要遵循這個約定的,一般情況下,約定分為總體約定和具體介面約定兩個部分,總體約定如下:

        1、以http的post方式提交資料,資料在$_POST中的鍵名為data;

        2、資料提交時需要先後經過json和base64編碼,中文需保持原樣,輸出的響應同樣如此;

        3、提交資料的格式:

$data = [

   'api' => 'userinfo', // 具體介面名稱,必須

   'noncestr'

=> '123',// 隨機字串,必須

   'serial' => '123', // 呼叫流水號,必須

'signature' => 'xxx', // 簽名,必須

   'body' => [

   ... // 請求詳細資料,由具體介面約定,可選

   ]

];

4、響應資料的格式:

$data = [

   'code' => 0, // 狀態碼,0為呼叫成功,非0為失敗,必須

   'serial' => '123', // 呼叫流水號,必須

'signature' => 'xxx', // 簽名,非必須

   'body' => [

   ... // 響應詳細資料,由具體介面約定,可選

   ]

];

以下是各檔案的程式碼:

aHttpApiHandler.php

<?php

namespace apis;
use libraries\HttpApiUtil;

/**
 * api處理器抽象類
 */
abstract class aHttpApiHandler {
	/**
	 * 請求資料
	 * @var array
	 */
	protected $data = [];
	
	/**
	 * 處理結果
	 * @var array
	 */
	protected $result = [];
	
	/**
	 * 處理請求
	 */
	abstract function handle();
	
	/**
	 * 構造方法
	 * @access public
	 * @param array $data
	 * @return void
	 */
	public function __construct($data) {
		$this->data = $data;
	}
	
	/**
	 * 輸出響應
	 * @access public
	 * @return mixed
	 */
	public function response() {
		$result = HttpApiUtil::makeReturn(0, '', $this->data['serial'], $this->result);
		echo base64_encode(json_encode($result, JSON_UNESCAPED_UNICODE));
	}	
}

TestApi.php

<?php

namespace apis;

/**
 * test介面
 */
class TestApi extends aHttpApiHandler {
	/**
	 * 處理請求
	 * @access public
	 * @return mixed
	 */
	public function handle() {
		echo '呼叫' . __CLASS__ . '處理請求', PHP_EOL;
	}
	
	/**
	 * 輸出響應
	 * @access public
	 * @return mixed
	 */
	public function response() {
		echo '呼叫' . __CLASS__ . '處理請求完畢,輸出響應', PHP_EOL;
	}
}

UserinfoApi.php

<?php

namespace apis;

/**
 * userinfo介面
 */
class UserinfoApi extends aHttpApiHandler {
	/**
	 * 處理請求
	 * @access public
	 * @return mixed
	 */
	public function handle() {
		// 模擬從資料庫取得使用者資訊
		$id = $this->data['body']['id'];
		$this->result = [
			'id' => $id,
			'name' => 'webmaster',
			'nickname' => 'web管理員'
		]; 
	}
}

common.php

<?php

/**
 * 類自動載入函式
 */
function api_autoload($class) {
	@include $class . '.php';
}

// 設定自動載入路徑
$includePath = ['apis', 'libraries'];
$includePath = implode(';', array_map(function($v){
	return ROOT_PATH . '/' . $v;
}, $includePath));
set_include_path($includePath);

// 註冊自動載入函式
spl_autoload_register('api_autoload');

CurlUtil.php

<?php

namespace libraries;

/**
 * curl工具類
 */
class CurlUtil {
	/**
	 * 以post方式獲取結果
	 * @access public
	 * @param string $url url地址
	 * @param string $data 資料
	 * @throws \Exception
	 * @return string
	 */
	public static function doPost($url, $data = null) {
		// 初始化
		if (!$ch = curl_init()) throw new \Exception('curl初始化失敗');
		
		// 設定選項
		curl_setopt($ch, CURLOPT_URL, $url);
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
		curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
		
		if ($data) {
			curl_setopt($ch, CURLOPT_POST, 1);
			curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
		}
		
		$result = curl_exec($ch);
		if (curl_errno($ch)) throw new \Exception(curl_error($ch));
		
		curl_close($ch);
		
		return $result;
	}
}

HttpApiConfig.php
<?php

namespace libraries;

/**
 * api配置類
 */
class HttpAPiConfig {
	/**
	 * 簽名key
	 * @var string
	 */
	const KEY = 'key for signature';
}

HttpApiError.php

<?php

namespace libraries;

/**
 * api錯誤定義
 */
class HttpApiError {
	/**
	 * 資料格式錯誤
	 * @var integer
	 */
	const INVALID_DATA = 10001;
	
	/**
	 * 資料解析錯誤
	 * @var integer
	 */
	const DATA_PARSE_ERROR = 10002;
	
	/**
	 * 簽名錯誤
	 * @var integer
	 */
	const INVALID_SIGNATURE = 10003;
	
	/**
	 * 介面不存在
	 * @var integer
	 */
	const INVALID_API = 10004;
	
	/**
	 * 無法建立處理器
	 * @var integer
	 */
	const CANT_CREATE_HANDLER = 10005;
	
	/**
	 * 未知錯誤
	 * @var integer
	 */
	const UNKNOWN_ERROR = 90001;
	
	/**
	 * 錯誤碼與錯誤資訊對映
	 * @var array
	 */
	private static $errors = [
		10001 => '資料格式錯誤',
		10002 => '資料解析錯誤',
		10003 => '簽名錯誤',
		10004 => '介面不存在',
		10005 => '無法建立處理器',
		90001 => '未知錯誤',
	];
	
	/**
	 * 獲取錯誤資訊
	 * @access public
	 * @param integer $code 錯誤碼
	 * @return mixed
	 */
	public static function getError($code) {
		return isset(self::$errors[$code]) ? self::$errors[$code] : false;
	}
}

HttpApiHandlerFactory.php

<?php

namespace libraries;

/**
 * api處理器工廠
 */
class HttpApiHandlerFactory {
	/**
	 * 生成api處理器例項
	 * @access public
	 * @param string $data
	 * @throws \Exception
	 * @return \libraries\aHttpApiHandler
	 */
	public static function create($data) {
		$info = HttpApiParser::parse($data);
		if ($info['code']) {
			throw new \Exception($info['msg'], $info['code']);
		}
		
		$request = $info['body'];
		$class = '\\apis\\' . ucfirst(strtolower($request['api'])) . 'Api';
		try {
			$rfc = new \ReflectionClass($class);
			return $rfc->newInstance($request);
		} catch (\Exception $e) {
			throw new \Exception(HttpApiError::getError(HttpApiError::CANT_CREATE_HANDLER), HttpApiError::CANT_CREATE_HANDLER);
		}		
	}
}

HttpApiParser.php
<?php

namespace libraries;

/**
 * api請求解析器
 */
class HttpApiParser {
	/**
	 * 解析請求
	 * @access public
	 * @param string $data
	 * @return array
	 */
	public static function parse($data) {
		if (!is_array($data) || !array_key_exists('data', $data)) {
				return HttpApiUtil::makeReturn(HttpApiError::INVALID_DATA, HttpApiError::getError(HttpApiError::INVALID_DATA));
		}
		
		$request = json_decode(base64_decode($data['data']), true);
		if (json_last_error()) {
			return HttpApiUtil::makeReturn(HttpApiError::UNKNOWN_ERROR, HttpApiError::getError(HttpApiError::UNKNOWN_ERROR));
		}
		
		// 檢查資料格式
		if (empty($request['api'])
			|| empty($request['noncestr'])
			|| empty($request['serial'])
			|| empty($request['signature'])) {
				return HttpApiUtil::makeReturn(HttpApiError::INVALID_DATA, HttpApiError::getError(HttpApiError::INVALID_DATA));
		}
		
		// 檢查簽名
		if ($request['signature'] != HttpApiUtil::makeSign($request)) {
			return HttpApiUtil::makeReturn(HttpApiError::INVALID_SIGNATURE, HttpApiError::getError(HttpApiError::INVALID_SIGNATURE), $request['serial']);
		}
		
		return HttpApiUtil::makeReturn(0, '', $request['serial'], $request);
	}
}

index.php
<?php

// 接收請求

use libraries\HttpApiHandlerFactory;
use libraries\HttpApiUtil;

define('ROOT_PATH', __DIR__);
include ROOT_PATH . '/common/common.php';

// **************************************

// 建立處理器
try {
	$api = HttpApiHandlerFactory::create($_POST);
} catch (\Exception $e) {
	print_r(HttpApiUtil::makeReturn($e->getCode(), $e->getMessage()));
	exit();
}

// 執行操作
$api->handle();
$api->response();

test.php
<?php

// 發起呼叫

use libraries\HttpApiUtil;
use libraries\CurlUtil;

define('ROOT_PATH', __DIR__);
include ROOT_PATH . '/common/common.php';

// **************************************

$url = 'http://api.local.com/index.php';
$data = [
	'api' => 'test',
	'noncestr' => HttpApiUtil::makeNoncestr(),
	'serial' => HttpApiUtil::makeSerial(),
	'body' => [
	]
];
$data['signature'] = HttpApiUtil::makeSign($data);

$json = 'data=' . base64_encode(json_encode($data));
try {
	$response = CurlUtil::doPost($url, $json);
	echo $response;//  原始輸出
	print_r(json_decode(base64_decode($response), true));
} catch (Exception $e) {
	echo $e->getMessage();
}

OK,簡易的http介面就是這樣了,如果要新增一個具體介面,比如檢視使用者列表,可以考慮新增一個apis\UserlistApi類,繼承aHttpApiHandler類並實現相應的方法即可了。