簡易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'
'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類並實現相應的方法即可了。