使用PHP CURL上传文件至AWS S3返回400错误求排查
问题描述
补充说明
我调试PHP和Golang代码时碰到大麻烦,但直接发Curl请求能解决问题:
curl -v -F $Signature -F $Date -F $Credential -F $Key -F $Algo -F $Policy -F $File $Url
我试着把这个请求转成PHP代码,但一直不行,现在只能用shell_exec()来执行这个请求。
补充说明结束
我的服务商没给AWS S3访问密钥,只允许通过AWS规定的表单上传文件,我想实现程序自动化上传。这个表单能正常使用,但用PHP CURL模拟请求时一直返回400错误。我已经删掉AWS示例里不必要的部分,尽量让CURL请求和表单一致,有人能指出问题在哪吗?
可用的HTML表单
<form action="https://<CLIENT NAME>.s3.amazonaws.com/" method="post" enctype="multipart/form-data"> <input type="input" name="key" value="something.pdf" /><br /> <input type="text" name="X-Amz-Credential" value="<SOME key>/20221017/REGION/s3/aws4_request" /> <input type="text" name="X-Amz-Algorithm" value="AWS4-HMAC-SHA256" /> <input type="text" name="X-Amz-Date" value="20221017T162325Z" /> <input type="hidden" name="X-Amz-Signature" value='<SIGNATURE>' /> <input type="hidden" name="Policy" value="<POLICY>" /> <input type="file" name="file" /> <br /> <input type="submit" name="submit" value="Upload to Amazon S3" /> </form>
我尝试的PHP-Curl示例代码
<?php if (isset($_POST['btnUpload'])) { $url = "https://<SERVICE PROVIDER>.s3.amazonaws.com/"; $filename = $_FILES['file']['name']; $filedata = $_FILES['file']['tmp_name']; $filesize = $_FILES['file']['size']; if ($filedata != '') { $headers = array( "enctype: multipart/form-data", 'Accept-Language: en-US,en;q=0.5', 'Connection: keep-alive', 'Upgrade-Insecure-Requests: 1', "Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7", "Keep-Alive: 300", "Connection: keep-alive" ); $postfields = array( "file" => "", "x-amz-signature" => "<SIGNATURE>", "x-amz-date" => "20221017T173731Z", "x-amz-credential" => "<EY>/20221017/ap-south-1/s3/aws4_request", "key" => "<SAMPLE.FILE>", "policy" => "<POLICY>", "x-amz-algorithm" => "AWS4-HMAC-SHA256" ); $ch = curl_init(); $options = array( CURLOPT_URL => $url, CURLOPT_HEADER => true, CURLOPT_POST => 1, CURLOPT_HTTPHEADER => $headers, CURLOPT_POSTFIELDS => $postfields, CURLOPT_INFILESIZE => $filesize, CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => false, CURLINFO_HEADER_OUT => true, CURLOPT_USERAGENT=>'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.1370.42', CURLOPT_ENCODING=>'gzip, deflate, br' ); // cURL options curl_setopt_array($ch, $options); curl_exec($ch); if(!curl_errno($ch)) { $info = curl_getinfo($ch); if ($info['http_code'] == 200) $errmsg = "File uploaded successfully"; } else { $errmsg = curl_error($ch); } $info = curl_getinfo($ch); print_r($info); curl_close($ch); } } ?> <form action="" method="post" name="frmUpload" enctype="multipart/form-data"> <tr> <td>Upload</td> <td align="center">:</td> <td><input name="file" type="file" id="file"/></td> </tr> <tr> <td> </td> <td align="center"> </td> <td><input name="btnUpload" type="submit" value="Upload" /></td> </tr>
问题分析与修复方案
你的PHP CURL代码存在这些核心问题:
- 文件字段未正确传递:
$postfields里把"file"设为空字符串,完全没把上传的文件传给S3。必须用CURLFile类指定文件路径、MIME类型和文件名,PHP才会正确生成多部分表单的文件上传格式。 - 参数大小写不匹配:HTML表单里的参数名是
X-Amz-Credential、X-Amz-Date这种驼峰格式,但PHP里用了全小写的x-amz-credential、x-amz-date。AWS S3的表单签名严格匹配参数名大小写,必须和表单完全一致。 - 手动设置
enctype头部多余且错误:当CURLOPT_POSTFIELDS传入数组时,PHP会自动生成带边界符的multipart/form-data头部,手动添加会覆盖自动生成的内容,导致S3无法解析表单。 - 重复的
Connection头部:$headers里同时存在'Connection: keep-alive'和"Keep-Alive: 300",重复设置可能引发请求异常。
修改后的PHP代码示例
<?php if (isset($_POST['btnUpload'])) { $url = "https://<SERVICE PROVIDER>.s3.amazonaws.com/"; $filename = $_FILES['file']['name']; $filedata = $_FILES['file']['tmp_name']; $filetype = $_FILES['file']['type']; if ($filedata != '' && file_exists($filedata)) { // 只保留必要的请求头部 $headers = array( 'Accept-Language: en-US,en;q=0.5', 'Upgrade-Insecure-Requests: 1', "Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5", "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7" ); $postfields = array( // 正确传递文件 "file" => new CURLFile($filedata, $filetype, $filename), // 保持参数名和HTML表单完全一致的大小写 "X-Amz-Signature" => "<SIGNATURE>", "X-Amz-Date" => "20221017T173731Z", "X-Amz-Credential" => "<EY>/20221017/ap-south-1/s3/aws4_request", "key" => "<SAMPLE.FILE>", "Policy" => "<POLICY>", "X-Amz-Algorithm" => "AWS4-HMAC-SHA256" ); $ch = curl_init(); $options = array( CURLOPT_URL => $url, CURLOPT_HEADER => true, CURLOPT_POST => 1, CURLOPT_HTTPHEADER => $headers, CURLOPT_POSTFIELDS => $postfields, CURLOPT_RETURNTRANSFER => true, CURLOPT_SSL_VERIFYPEER => false, CURLINFO_HEADER_OUT => true, CURLOPT_USERAGENT=>'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.1370.42', CURLOPT_ENCODING=>'gzip, deflate, br' ); curl_setopt_array($ch, $options); $response = curl_exec($ch); if(!curl_errno($ch)) { $info = curl_getinfo($ch); if ($info['http_code'] == 200) { $errmsg = "文件上传成功"; } else { $errmsg = "请求失败,状态码:" . $info['http_code']; // 打印S3返回的具体错误信息,方便排查 echo "<pre>" . $response . "</pre>"; } } else { $errmsg = curl_error($ch); } echo $errmsg; print_r($info); curl_close($ch); } } ?> <form action="" method="post" name="frmUpload" enctype="multipart/form-data"> <table> <tr> <td>上传文件</td> <td align="center">:</td> <td><input name="file" type="file" id="file"/></td> </tr> <tr> <td> </td> <td align="center"> </td> <td><input name="btnUpload" type="submit" value="上传" /></td> </tr> </table> </form>
内容的提问来源于stack exchange,提问作者Chilarai
相关产品推荐
相关产品推荐

