首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP cURL 模擬 POST 上傳文件:從基礎用法到跨環境兼容性坑的完美解決

     編輯:PHP 相關知識

PHP cURL 模擬 POST 上傳文件:從基礎用法到跨環境兼容性坑的完美解決

項目需求:本地服務接收數據後轉發至另一台服務器,包含文件流。本文記錄從 @ 前綴法到手構 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);

問題:

  • Windows (WAMP) 成功,CentOS (Nginx) 失敗,對方 Java 服務返回「文件接收失敗」
  • 抓包發現:Windows 發送 Content-Type: multipart/form-data,CentOS 卻發 application/x-www-form-urlencoded
  • 手動設置 CURLOPT_HTTPHEADER => ['Content-Type: multipart/form-data'] 無效,cURL 會忽略並重置
  • PHP 5.3.x 版本微小差異導致行為不一致

二、根因:cURL 根據 POSTFIELDS 類型自動決定 Content-Type

  • 陣列且包含 @ 開頭值 → multipart/form-data
  • 陣列無 @ 或字串 → application/x-www-form-urlencoded
  • 某些環境下 @ 前綴失效(如 curl_file_create 引入後、CURLOPT_SAFE_UPLOAD 影響)

三、終極解決:手構 RFC 2388 multipart/form-data 體

參考 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+)

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 解決了 @ 前綴的安全問題(任意文件讀取風險)
  • PHP 7 默認 CURLOPT_SAFE_UPLOAD = true,@ 前綴已廢棄
  • 若需兼容 5.3/5.4,本文手構方案是最佳選擇
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved