呼叫 RESTful API、微服務、第三方平台 (如微信、Line Bot) 時,常需以 POST 發送 Content-Type: application/json 資料。本文提供封裝良好的 postJson() 函式,涵蓋編碼、Header、超時、錯誤碼解析、重試機制。
<?php
/**
* 發送 JSON POST 請求
* @param string $url 目標 URL
* @param array|object $data 要發送的資料 (自動 json_encode)
* @param array $options 額外選項
* @return array ['success'=>bool, 'code'=>int, 'body'=>string, 'headers'=>array, 'error'=>string]
*/
function postJson($url, $data, $options = []) {
$defaults = [
'timeout' => 10, // 秒
'connect_timeout' => 5,
'headers' => [], // 額外 Header
'retry' => 0, // 重試次數
'retry_delay' => 100000, // 微秒
'verify_ssl' => true, // 生產環境建議 true
];
$opts = array_merge($defaults, $options);
$payload = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if (json_last_error() !== JSON_ERROR_NONE) {
return ['success' => false, 'code' => 0, 'body' => '', 'headers' => [], 'error' => 'JSON 編碼失敗: ' . json_last_error_msg()];
}
$headers = array_merge([
'Content-Type: application/json; charset=utf-8',
'Content-Length: ' . strlen($payload),
'Accept: application/json',
], $opts['headers']);
$attempt = 0;
do {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true, // 回傳包含 Header
CURLOPT_TIMEOUT => $opts['timeout'],
CURLOPT_CONNECTTIMEOUT => $opts['connect_timeout'],
CURLOPT_SSL_VERIFYPEER => $opts['verify_ssl'],
CURLOPT_SSL_VERIFYHOST => $opts['verify_ssl'] ? 2 : 0,
CURLOPT_USERAGENT => 'PHP-cURL-JSON/1.0',
]);
$response = curl_exec($ch);
$errno = curl_errno($ch);
$error = curl_error($ch);
$info = curl_getinfo($ch);
curl_close($ch);
if ($errno === 0) {
// 成功收到回應 (含 HTTP 4xx/5xx)
$headerSize = $info['header_size'];
$respHeaders = substr($response, 0, $headerSize);
$body = substr($response, $headerSize);
$httpCode = $info['http_code'];
// 解析回應 Header 成陣列
$hdrArr = [];
foreach (explode("\r\n", $respHeaders) as $line) {
if (strpos($line, ':') !== false) {
list($k, $v) = explode(':', $line, 2);
$hdrArr[trim($k)] = trim($v);
}
}
return [
'success' => ($httpCode >= 200 && $httpCode < 300),
'code' => $httpCode,
'body' => $body,
'headers' => $hdrArr,
'error' => ($httpCode >= 200 && $httpCode < 300) ? '' : "HTTP $httpCode",
];
}
// 網路層錯誤:可重試
$attempt++;
if ($attempt <= $opts['retry']) {
usleep($opts['retry_delay']);
}
} while ($attempt <= $opts['retry']);
return ['success' => false, 'code' => 0, 'body' => '', 'headers' => [], 'error' => "cURL 錯誤 ($errno): $error"];
}
// 使用範例
$url = 'https://api.example.com/webhook';
$data = [
'button' => [
['type'=>'click', 'name'=>'今日歌曲', 'key'=>'V1001_TODAY_MUSIC'],
['type'=>'click', 'name'=>'歌手簡介', 'key'=>'V1001_TODAY_SINGER'],
['name'=>'選單', 'sub_button'=>[
['type'=>'click', 'name'=>'Hello', 'key'=>'V1001_HELLO_WORLD'],
['type'=>'click', 'name'=>'贊', 'key'=>'V1001_GOOD'],
]],
],
];
$result = postJson($url, $data, ['timeout'=>15, 'retry'=>2]);
if ($result['success']) {
echo "成功: " . $result['body'];
} else {
error_log("發送失敗: " . $result['error']);
echo "失敗: " . $result['error'];
}
?>
| 選項 | 預設 | 用途 |
|---|---|---|
timeout | 10 | 整個請求最長秒數 (含傳輸) |
connect_timeout | 5 | TCP 連線建立最長秒數 |
verify_ssl | true | 驗證 SSL 憑證,生產必開;測試可關 |
retry | 0 | 網路錯誤時重試次數 (不重試 HTTP 4xx/5xx) |
headers | [] | 額外 Header,如 Authorization: Bearer xxx |
php://input,需用 json_decode(file_get_contents('php://input'), true) 讀取,而非 $_POST。json_encode(..., JSON_UNESCAPED_UNICODE) 且 Header 含 charset=utf-8;資料庫/檔案儲存亦為 UTF-8。curl.cainfo=/path/to/cacert.pem (php.ini) 或 CURLOPT_CAINFO。CURLOPT_READFUNCTION 流式讀取,或分批發送。function postJsonMulti(array $requests, $options = []) {
// $requests = [['url'=>'...', 'data'=>[...]], ...]
$mh = curl_multi_init();
$handles = [];
foreach ($requests as $i => $req) {
$ch = curl_init($req['url']);
$payload = json_encode($req['data'], JSON_UNESCAPED_UNICODE);
curl_setopt_array($ch, [
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Content-Length: '.strlen($payload)],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HEADER => true,
] + ($options['curl_opts'] ?? []));
curl_multi_add_handle($mh, $ch);
$handles[$i] = $ch;
}
$running = null;
do { curl_multi_exec($mh, $running); } while ($running);
$results = [];
foreach ($handles as $i => $ch) {
$resp = curl_multi_getcontent($ch);
$info = curl_getinfo($ch);
// 解析同單一版本...
$results[$i] = parseResponse($resp, $info);
curl_multi_remove_handle($mh, $ch);
}
curl_multi_close($mh);
return $results;
}