架構概覽
- 傳輸層:HTTP/HTTPS (cURL)
- 協定:RESTful (資源導向、狀態無關)
- 資料格式:JSON
- 認證:Token + 簽名驗證 (Client_Token + Client_Code)
- 路由模式:PATH_INFO (
/resource/id)
核心路由映射
| HTTP 方法 | PATH_INFO | 語意 | 對應方法 |
| GET | /users | 列出所有使用者 (分頁) | _get() |
| POST | /users | 建立新使用者 | _post() |
| GET | /users/123 | 取得 ID=123 使用者詳情 | _get() |
| PUT | /users/123 | 更新 ID=123 使用者 | _put() |
| DELETE | /users/123 | 刪除 ID=123 使用者 | _delete() |
伺服器端:ApiServer 基礎類別
<?php
abstract class ApiServer {
private string $method = '';
protected array $params = [];
protected string $resource = '';
protected string $resourceId = '';
public function __construct() {
$this->authorize(); // 驗證簽名
$this->method = strtolower($_SERVER['REQUEST_METHOD']);
$this->parsePathInfo(); // 解析資源與 ID
$this->collectParams(); // 收集參數 (GET/POST/PUT body)
$this->dispatch(); // 分發處理
}
private function authorize(): void {
$token = $_SERVER['HTTP_CLIENT_TOKEN'] ?? '';
$code = $_SERVER['HTTP_CLIENT_CODE'] ?? '';
$expected = md5(substr(md5($token), 8, 24) . $token);
if ($expected !== $code) {
$this->output(1); // 驗證失敗
exit;
}
}
private function parsePathInfo(): void {
$path = $_SERVER['PATH_INFO'] ?? '/';
$parts = array_filter(explode('/', trim($path, '/')));
$this->resource = $parts[0] ?? '';
$this->resourceId = $parts[1] ?? '';
}
private function collectParams(): void {
switch ($this->method) {
case 'get':
case 'delete':
$this->params = $_GET;
break;
case 'post':
$this->params = $_POST;
break;
case 'put':
parse_str(file_get_contents('php://input'), $this->params);
break;
}
}
private function dispatch(): void {
$handler = '_' . $this->method;
if (method_exists($this, $handler)) {
$this->$handler();
} else {
$this->output(2, ['error' => 'Method Not Allowed']);
}
}
// 子類別必須實現
abstract protected function _get(): void;
abstract protected function _post(): void;
abstract protected function _put(): void;
abstract protected function _delete(): void;
protected function output(int $status, array $data = []): void {
$messages = [
0 => ['code' => 1, 'info' => '成功', 'data' => $data],
1 => ['code' => 0, 'info' => '驗證失敗'],
2 => ['code' => 0, 'info' => '不支援的方法'],
];
header('Content-Type: application/json; charset=utf-8');
echo json_encode($messages[$status] ?? ['code' => 0, 'info' => '未知狀態']);
}
}
客戶端:RestClient 封裝
<?php
class RestClient {
private string $url;
private string $method;
private array $data;
private resource $ch;
private array $headers = [];
private const TOKEN = 'your_secret_token';
public function __construct(string $url, array $data = [], string $method = 'get') {
$this->url = $url;
$this->method = strtolower($method);
$this->data = $data;
// GET 參數附加到 URL
if ($this->method === 'get' && !empty($data)) {
$this->url .= '?' . http_build_query($data);
}
$this->ch = curl_init($this->url);
curl_setopt_array($this->ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $this->buildHeaders(),
]);
}
private function buildHeaders(): array {
$headers = ['Content-Type: application/json'];
if (self::TOKEN) {
$code = md5(substr(md5(self::TOKEN), 8, 24) . self::TOKEN);
$headers[] = 'Client_Token: ' . self::TOKEN;
$headers[] = 'Client_Code: ' . $code;
}
return $headers;
}
public function execute(): string|false {
$opts = [
'post' => [CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($this->data)],
'put' => [CURLOPT_CUSTOMREQUEST => 'PUT', CURLOPT_POSTFIELDS => json_encode($this->data)],
'delete' => [CURLOPT_CUSTOMREQUEST => 'DELETE'],
'get' => [CURLOPT_HTTPGET => true],
];
curl_setopt_array($this->ch, $opts[$this->method] ?? $opts['get']);
return curl_exec($this->ch);
}
public function getInfo(): array {
return curl_getinfo($this->ch);
}
public function __destruct() {
if ($this->ch) curl_close($this->ch);
}
}
使用範例
測試伺服器
<?php
// testServer.php
require 'ApiServer.php';
class TestServer extends ApiServer {
protected function _get(): void {
$this->output(0, ['method' => 'GET', 'resource' => $this->resource, 'id' => $this->resourceId]);
}
protected function _post(): void { $this->output(0, ['method' => 'POST', 'data' => $this->params]); }
protected function _put(): void { $this->output(0, ['method' => 'PUT', 'id' => $this->resourceId, 'data' => $this->params]); }
protected function _delete(): void { $this->output(0, ['method' => 'DELETE', 'id' => $this->resourceId]); }
}
new TestServer();
測試客戶端
<?php
// testClient.php
require 'RestClient.php';
$client = new RestClient('https://api.example.com/users/123', [], 'get');
$response = $client->execute();
$info = $client->getInfo();
echo "HTTP Code: {$info['http_code']}\n";
echo "Response: $response\n";
/* 輸出範例:
HTTP Code: 200
Response: {"code":1,"info":"成功","data":{"method":"GET","resource":"users","id":"123"}}
*/
部署注意事項
- PATH_INFO 支援:Nginx 需設定
fastcgi_split_path_info;Apache 需啟用 AcceptPathInfo On。
- PUT/DELETE 請求體:PHP 不自動解析,需手動
file_get_contents('php://input') + parse_str 或 json_decode。
- HTTPS:生產環境強制 TLS,cURL 設定
CURLOPT_SSL_VERIFYPEER => true。
- 錯誤處理:統一 JSON 格式回傳,包含 code、message、data。
- 速率限制:建議加入 API 節流 (Token Bucket / Leaky Bucket)。