
本文详解 php 通过 curl put 方式上传文件至 node.js express 接口时的常见错误及完整解决方案,涵盖 php 端数据构造、node.js 端请求解析、中间件配置与二进制流处理要点。
本文详解 php 通过 curl put 方式上传文件至 node.js express 接口时的常见错误及完整解决方案,涵盖 php 端数据构造、node.js 端请求解析、中间件配置与二进制流处理要点。
PHP 原代码使用 fopen() + CURLOPT_PUT 直接传递文件指针,看似简洁,但存在根本性误解:CURLOPT_INFILE 仅支持原始二进制流上传(即裸 payload),不会自动构造 multipart/form-data 或任何带边界(boundary)的表单格式;而 Express 默认的 body-parser 中间件(如 bodyParser.json() 或 bodyParser.urlencoded())完全无法解析原始 PUT 流体——它只处理符合特定 Content-Type 的结构化请求体(如 application/json 或 application/x-www-form-urlencoded)。因此 req.body 恒为 undefined 是预期行为,而非 bug。
要实现可靠文件传输,推荐两种专业级方案:
✅ 方案一:PHP 发送原始二进制流,Node.js 显式读取 req 流(轻量高效)
适用于 WAV、MP3 等纯二进制文件,无需额外依赖。
PHP 端(修正后):
立即学习“PHP免费学习笔记(深入)”;
使用 JSON Schema 验证 JSON 数据,从示例 JSON 生成 schema,并将其转换为 TypeScript 接口、Python 数据类或 Markdown 文档。
$fileContent = file_get_contents('file.wav'); // ✅ 获取原始字节流
$ex_id = getExternalId();
$apiurl = 'http://url.to.the.second.part/api/upload?id=' . $ex_id . '&oneway=true';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $apiurl);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); // 显式设为 PUT
curl_setopt($ch, CURLOPT_POSTFIELDS, $fileContent); // ✅ 直接传入二进制内容
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: audio/wav', // ⚠️ 必须声明真实 MIME 类型
'Content-Length: ' . strlen($fileContent)
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);Node.js 端(Express 配置):
const express = require('express');
const app = express();
// ❌ 移除所有 body-parser 中间件(它们对 raw body 无效)
// app.use(express.json());
// app.use(express.urlencoded({ extended: true }));
// ✅ 启用原始流解析(关键!)
app.use(express.raw({
type: ['audio/wav', 'audio/mp3', 'application/octet-stream'],
limit: '50mb'
}));
app.put('/api/upload', (req, res) => {
const externalId = req.query.id;
const oneway = req.query.oneway === 'true';
// ✅ 此时 req.body 是 Buffer 类型的原始文件内容
const fileBuffer = req.body;
console.log(`Received ${fileBuffer.length} bytes for ID: ${externalId}`);
// 示例:保存文件(生产环境请加校验、防覆盖、路径安全等)
const fs = require('fs');
fs.writeFileSync(`./uploads/${externalId}.wav`, fileBuffer);
res.status(200).json({ success: true, size: fileBuffer.length });
});⚠️ 方案二:改用 POST + multipart/form-data(更通用,但需额外中间件)
若需同时传文件+其他字段(如 metadata),或需兼容浏览器上传,建议改用标准 POST + multipart。
PHP 端(使用 curl_file_create):
$ex_id = getExternalId();
$apiurl = 'http://url.to.the.second.part/api/upload';
$postData = [
'id' => $ex_id,
'oneway' => 'true',
'file' => curl_file_create('file.wav', 'audio/wav') // ✅ PHP 5.5+ 原生支持
];
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $apiurl);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$result = curl_exec($ch);
curl_close($ch);Node.js 端(需 multer):
npm install multer
const multer = require('multer');
const upload = multer({ dest: './uploads/' });
app.post('/api/upload', upload.single('file'), (req, res) => {
const externalId = req.body.id;
const file = req.file; // ✅ multer 解析后的文件对象
if (!file) {
return res.status(400).json({ error: 'No file uploaded' });
}
// 可选:重命名文件
const newPath = `./uploads/${externalId}${path.extname(file.originalname)}`;
fs.renameSync(file.path, newPath);
res.json({ success: true, path: newPath });
});? 关键注意事项
-
永远显式设置
Content-Type:PHP 端CURLOPT_HTTPHEADER和 Node.jsexpress.raw({type: [...]})必须匹配,否则 Express 拒绝解析。 -
禁用冲突中间件:
express.json()和express.urlencoded()会消费请求流,导致后续express.raw()读取为空——务必按顺序注册,且仅保留所需的一种。 -
流式处理大文件:生产环境应使用
req.on('data')事件流式写入磁盘,避免内存溢出;express.raw()默认将整个 body 加载进内存。 -
安全性:验证
req.query.id格式、限制文件大小(limit)、校验文件头(magic bytes)、过滤非法扩展名,防止任意文件上传。
选择方案一可最小化依赖并精准控制二进制传输;方案二则提供更丰富的文件元数据支持。二者均能彻底解决 req.body === undefined 的核心问题。


















