如何从PHP/PSR-7 ServerRequestInterface获取multipart payload子部分头
发送multipart/form-data请求时,每个表单部分可携带额外请求头。例如以下curl命令:
curl -v --trace-ascii - -X POST https://dev-server/api/intake -H "Authorization: Bearer $token" -F 'data={"json":{"Yep": "this is JSON"}};type=application/json'
会生成如下HTTP请求:
POST /api/intake HTTP/2 Host: dev-server User-Agent: curl/8.5.0 Accept: */* Authorization: Bearer long-token-here Content-Length: 213 Content-Type: multipart/form-data; boundary=------------------------lQNQ3xgpz1neU0tlQTPXnF --------------------------lQNQ3xgpz1neU0tlQTPXnF Content-Disposition: form-data; name="data" Content-Type: application/json {"json":{"Yep": "this is JSON"}} --------------------------lQNQ3xgpz1neU0tlQTPXnF--
我用PHP实现API端点,通过PSR-7的ServerRequestInterface捕获请求数据,能通过$message->getParsedBody()获取data字段的JSON字符串,但无法获取其关联的Content-Type: application/json子头。
尝试自行解析请求体也未成功:
// 返回空字符串,推测是因为 multipart/form-data 请求下 php://input 不可用 $message->getBody()->getContents(); // 无法全局禁用 php.ini 中的 enable_post_data_reading 设置, // 因为应用其他部分依赖它,且通过 ini_set 修改无效。
请问是否有官方支持的方式获取这些子部分头?(我知晓可将整个请求设为application/json,但这并非目标,我希望使用multipart请求。)
PHP默认处理multipart/form-data请求时,仅解析表单字段内容与文件上传元数据,不会保留非文件字段的子头信息。基于PSR-7规范,可通过以下两种官方支持的方式解决:
1. 将字段标记为文件上传
在curl请求中给目标字段添加filename=标记(即使没有实际文件),PHP会将其识别为上传文件,子头信息会被保留:
修改后的curl命令:
curl -v --trace-ascii - -X POST https://dev-server/api/intake -H "Authorization: Bearer $token" -F 'data={"json":{"Yep": "this is JSON"}};type=application/json;filename='
或通过标准输入传递内容:
curl -v --trace-ascii - -X POST https://dev-server/api/intake -H "Authorization: Bearer $token" -F 'data=@-;type=application/json' <<<'{"json":{"Yep": "this is JSON"}}'
随后在PSR-7请求对象中,通过getUploadedFiles()方法获取子头与内容:
$uploadedFiles = $message->getUploadedFiles(); if (isset($uploadedFiles['data'])) { $contentType = $uploadedFiles['data']->getClientMediaType(); // 获取Content-Type $jsonContent = $uploadedFiles['data']->getStream()->getContents(); // 获取内容 }
2. 使用PSR-7兼容的请求解析库
若无法修改客户端请求,可使用成熟的PSR-7请求解析库(如symfony/http-foundation的PSR-7桥接工具、laminas/laminas-httphandlerrunner),这些库能完整解析multipart/form-data的所有子部分及其头信息。
以symfony/http-foundation为例,将PSR-7请求转换为Symfony Request后即可获取完整表单部分信息:
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Psr7\HttpFoundationPsr7Bridge; // 转换PSR-7请求为Symfony Request $symfonyRequest = HttpFoundationPsr7Bridge::createRequest($message); // 遍历所有表单部分 foreach ($symfonyRequest->getPayload()->getParts() as $part) { if ($part->getName() === 'data') { $contentType = $part->getHeaders()['Content-Type'][0]; $content = $part->getBody()->getContents(); break; } }
这类库会自动处理php://input的读取时机,无需修改enable_post_data_reading配置。
内容的提问来源于stack exchange,提问作者DMJ

