項目需求:本地服務接收數據後轉發至另一台服務器,包含文件流。本文記錄從 @ 前綴法到手構 multipart/form-data 體的完整踩坑過程,解決 Windows/Linux 環境差異、PHP 版本差異導致的上傳失敗問題。
@ 前綴)及其局限$postData = [
'param1' => 'value1',
'file' => '@' . $absolutePath // @ + 絕對路徑
];
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $postData,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 300,
CURLOPT_BINARYTRANSFER => true,
]);
$result = curl_exec($ch);
問題:
Content-Type: multipart/form-data,CentOS 卻發 application/x-www-form-urlencodedCURLOPT_HTTPHEADER => ['Content-Type: multipart/form-data'] 無效,cURL 會忽略並重置@ 開頭值 → multipart/form-data@ 或字串 → application/x-www-form-urlencoded@ 前綴失效(如 curl_file_create 引入後、CURLOPT_SAFE_UPLOAD 影響)參考 PHP 官方範例,完全控制請求體格式,繞過 cURL 自動生成邏輯。
function postData($url, $data = [], $files = []) {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_BINARYTRANSFER => true,
]);
curl_custom_postfields($ch, $data, $files);
$resp = curl_exec($ch);
curl_close($ch);
return $resp;
}
/**
* 安全構建 multipart/form-data(PHP 5.3 - 5.4 兼容)
* @param resource $ch cURL handle
* @param array $assoc 普通字段 [name => value]
* @param array $files 文件字段 [name => absolute_path]
*/
function curl_custom_postfields($ch, array $assoc = [], array $files = []) {
$disallow = ["\0", "\r", "\n", '"']; // 非法字符
$body = [];
// 普通字段
foreach ($assoc as $k => $v) {
$k = str_replace($disallow, '_', $k);
$body[] = implode("\r\n", [
'Content-Disposition: form-data; name="' . $k . '"',
'',
filter_var($v),
]);
}
// 文件字段
foreach ($files as $k => $v) {
$v = realpath(filter_var($v));
if (!$v || !is_file($v) || !is_readable($v)) continue;
$data = file_get_contents($v);
$filename = basename($v);
$k = str_replace($disallow, '_', $k);
$filename = str_replace($disallow, '_', $filename);
$body[] = implode("\r\n", [
'Content-Disposition: form-data; name="' . $k . '"; filename="' . $filename . '"',
'Content-Type: application/octet-stream',
'',
$data,
]);
}
// 生成隨機 boundary
do {
$boundary = '---------------------' . md5(mt_rand() . microtime());
} while (preg_grep("/{$boundary}/", $body));
// 組裝最終 body
array_walk($body, function (&$part) use ($boundary) {
$part = "--{$boundary}\r\n{$part}";
});
$body[] = "--{$boundary}--";
$body[] = '';
$postBody = implode("\r\n", $body);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $postBody,
CURLOPT_HTTPHEADER => [
'Expect: 100-continue',
"Content-Type: multipart/form-data; boundary={$boundary}",
],
]);
}
$result = postData(
'https://target-api/upload',
['user_id' => 123, 'action' => 'avatar'], // 普通字段
['avatar' => '/var/www/uploads/avatar.jpg'] // 文件字段(絕對路徑)
);
| 特性 | @ 前綴法 | 手構 Body 法 |
|---|---|---|
| 跨平台一致性 | 不穩定 | 完全可控 |
| 文件名控制 | 僅路徑basename | 可自定義 filename |
| Content-Type 控制 | 無法控制 | 精確指定 boundary |
| 大文件記憶體 | 需 CURLOPT_INFILE | 可改用流式讀取 |
| PHP 版本要求 | 5.0+ | 5.3+ (需 filter_var) |
PHP 5.5 引入 CURLFile 類,官方推薦替代 @ 前綴:
$postData = [
'param1' => 'value1',
'file' => new CURLFile('/absolute/path/to/file.jpg', 'image/jpeg', 'custom_name.jpg')
];
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData); // 自動生成正確 multipart
CURLFile 解決了 @ 前綴的安全問題(任意文件讀取風險)CURLOPT_SAFE_UPLOAD = true,@ 前綴已廢棄