我们使用 https://pecl.php.net/package/apfd. 解决了我们的问题。
它使用 PUT 和 PATCH HTTP 请求解析 multipart/form-data 主体(文件和有效负载),而之前只能使用 POST HTTP 请求解析。
PHP 支持某些客户端用于将文件存储在服务器上的 HTTP PUT 方法。PUT 请求比使用 POST 请求的文件上传简单得多,它们看起来像这样
PUT /path/filename.html HTTP/1.1
这通常意味着远程客户端希望将后续内容保存为:/path/filename.html 在您的 Web 树中。显然,让 Apache 或 PHP 自动让每个人覆盖 Web 树中的任何文件不是一个好主意。因此,要处理此类请求,您首先必须告诉您的 Web 服务器您希望某个 PHP 脚本处理该请求。在 Apache 中,您可以使用 Script 指令执行此操作。它可以放置在 Apache 配置文件中的几乎任何位置。一个常见的位置是在 <Directory>
块内,或者可能是在 <VirtualHost>
块内。像这样的一行就可以做到
Script PUT /put.php
这告诉 Apache 将所有对与您放置此行的上下文中匹配的 URI 的 PUT 请求发送到 put.php 脚本。当然,这假设您为 .php 扩展启用了 PHP,并且 PHP 处于活动状态。所有对该脚本的 PUT 请求的目标资源必须是该脚本本身,而不是上传文件应具有的文件名。
然后,使用 PHP,您将在您的 put.php 中执行以下操作。这会将上传文件的内容复制到服务器上的 myputfile.ext 文件。您可能希望在执行此文件复制之前执行一些检查和/或验证用户身份。
示例 #1 保存 HTTP PUT 文件
<?php
/* PUT 数据来自 stdin 流 */
$putdata = fopen("php://input", "r");
/* 打开一个用于写入的文件 */
$fp = fopen("myputfile.ext", "w");
/* 一次读取 1 KB 的数据
并写入文件 */
while ($data = fread($putdata, 1024))
fwrite($fp, $data);
/* 关闭流 */
fclose($fp);
fclose($putdata);
?>
我们使用 https://pecl.php.net/package/apfd. 解决了我们的问题。
它使用 PUT 和 PATCH HTTP 请求解析 multipart/form-data 主体(文件和有效负载),而之前只能使用 POST HTTP 请求解析。
这对我有用。网络上有很多无效的示例。我在 https://lornajane.net/posts/2009/putting-data-fields-with-php-curl. 中找到了。
重要事项:您不应该使用以下代码
curl_setopt($ch, CURLOPT_PUT, true);
即使它似乎是正确的选项(对于 POST 请求来说,CURLOPT_POST 应该是正确的选项,但它不适用于 PUT 请求)。
请注意,使用了常量 CURLOPT_CUSTOMREQUEST 而不是 CURLOPT_PUT,并且使用的是 "PUT" 而不是 true。
<?php
$url = "....."; // 在此处输入您的 URL
$data = array("a" => $a);
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT");
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
$response = curl_exec($ch);
if ( ! $response) {
return false;
}
你好 PHP 世界经过数小时的担心 :=)
我找到了恢复或暂停上传的解决方案
在此代码段中,是服务器端而不是客户端在任何桌面程序上,您必须使用字节范围来计算已上传的字节和总字节数的缺失。
这是 PHP 代码
<?php
$CHUNK = 8192;
try {
if (!($putData = fopen("php://input", "r")))
throw new Exception("无法获取 PUT 数据");
// 现在参数可以像其他变量一样使用
// 请参见输入完成后的代码
$tot_write = 0;
$tmpFileName = "/var/dev/tmp/PUT_FILE";
// 创建临时文件
if (!is_file($tmpFileName)) {
fclose(fopen($tmpFileName, "x")); // 创建文件并关闭
// 打开文件以供写入
if (!($fp = fopen($tmpFileName, "w")))
throw new Exception("无法写入临时文件");
// 逐块读取数据并写入文件
while ($data = fread($putData, $CHUNK)) {
$chunk_read = strlen($data);
if (($block_write = fwrite($fp, $data)) != $chunk_read)
throw new Exception("无法写入更多数据到临时文件");
$tot_write += $block_write;
}
if (!fclose($fp))
throw new Exception("无法关闭临时文件");
unset($putData);
} else {
// 打开文件以供写入
if (!($fp = fopen($tmpFileName, "a")))
throw new Exception("无法写入临时文件");
// 逐块读取数据并写入文件
while ($data = fread($putData, $CHUNK)) {
$chunk_read = strlen($data);
if (($block_write = fwrite($fp, $data)) != $chunk_read)
throw new Exception("无法写入更多数据到临时文件");
$tot_write += $block_write;
}
if (!fclose($fp))
throw new Exception("无法关闭临时文件");
unset($putData);
}
// 检查文件长度和 MD5
if ($tot_write != $file_size)
throw new Exception("文件大小错误");
$md5_arr = explode(' ', exec("md5sum $tmpFileName"));
$md5 = $md5sum_arr[0];
if ($md5 != $md5sum)
throw new Exception("MD5 错误");
} catch (Exception $e) {
echo '', $e->getMessage(), "\n";
}
?>
我对使用 PUT 方法上传文件感到困惑。
我的疑问是,为什么我们不能使用 PUT 方法和流上传多个文件
PUT 数据通过 stdin 流传入
$putdata = fopen("php://input", "r");
请注意,$putdata 是指向正在上传的文件内容的文件指针。
数据在服务器端动态接收(意味着它在接收时可用)
其次,当我们使用 parse_str(file_get_contents("php://input")) 时。
这意味着数据已完全接收在服务器端,然后才能提供给脚本。
使用 fopen() 时无法解析数据。当上传大型文件时,可以使用此方法。
文件大小可能从数百兆字节到千兆字节不等,而流在这方面起着重要作用。
流使脚本能够分块访问文件数据,而不是先将其保存在临时文件夹中。
因此,使用 $putdata = fopen("php://input", "r"); 时,不能传递有效负载。
如果有人想要传递有效负载,唯一的选择是在 URL 查询字符串中。
我找到的所有使用 PHP 的 PUT 方法的示例代码都为传入流使用默认的硬编码文件扩展名。
来自传入文件 PUT 请求的文件名在传入请求中找不到(至少我找不到),但 MIME 类型可以在 $_SERVER 全局变量中找到。
我使用以下代码获取正确的文件扩展名
$mimeType = $_SERVER['HTTP_CONTENT_TYPE'];
if ($mimeType!='application/pdf')
{
header('HTTP/1.1 405 Only PDF files allowed');
echo("Only PDF files are allowed for upload - this file is ".$mimeType);
die();
}
else $fileExtension = 'pdf';
如果您有可用的 Apache Tika 服务器,这将是分析文件内容以获取 MIME 类型的最佳选择,但对于每个人来说可能并非如此 :-)
PUT 原始数据在 php://input 中,您必须使用 fopen() 和 fread() 来获取内容。file_get_contents() 无用。
HTTP PUT 请求必须包含 Content-Length 标头以指定主体长度(以字节为单位),否则服务器将无法知道输入流何时结束。这是许多人发现 php://input 为空的原因,因为没有此类标头可用。
这应该使 PUT 在使用 PHP 5.1.1 和 apache2 的 win32 上正常工作。
案例研究:要将 Netscape 7.2 Composer 设置为发布到 Apache/PHP,无需使用 CGI(我之前尝试过很长时间但没有成功),也不需要更改 Apache 的 httpd.conf。我只需要单击“发布为”,将 put2disk.php 作为文件名填写(其内容如下所示),并将该文件的目录作为“发布地址”填写。
XAMPP 1.4.14:Apache/2.0.54 (Win32) mod_ssl/2.0.54 OpenSSL/0.9.7g PHP/5.0.4。
<? // 文件名:put2disk.php。
//file_put_contents ("get_def.out", print_r (get_defined_vars(), TRUE)); // 调试
// 两种吸取方法:(a) 未成功,(b) 成功。
//$stdin_rsc = fopen("php://input", "r");
//$putdata='';
//while ($putdata .= fread($stdin_rsc, 1024)); // a. 使“发布...” 对话框挂起。
//while (!feof($stdin_rsc)) $putdata.=fread($stdin_rsc, 8192); // b. 成功,但 file_get_contents 速度更快。
//fclose($stdin_rsc);
// 所有必需的内容
$putdata=file_get_contents('php://input'); // 不是 php://stdin!(当无法查看错误消息时,文档(本手册页面)需要更准确。)
file_put_contents("stdin.out",$putdata);
?>
注意:<Script> 指令不能放在 .htaccess 文件中。
因此,如果您共享网络空间且无法访问 apache 配置文件,您将很难让类似的功能正常工作。
但是,您可以使用 mod_rewrite(适用于 Apache)解决问题,有关更多信息,请参阅 https://httpd.apache.org/docs/2.0/mod/mod_rewrite.html 中的文档。
我花费了大量时间试图让 PUT 在 Apache 2.0.40 上正常工作。我还没有找到任何方法让 Script 指令通过 mod_php 调用 php,唯一的方法是创建一个名为 example.cgi 的文件,并通过 CGI 调用它,该文件从
#!/usr/bin/php
开始,这样 PHP 解释器通过 CGI 机制调用,而不是作为模块。
如果有让它“正确”工作的方法,我非常想知道!经过六个小时的折腾,我选择了 CGI。apache 错误日志中的错误消息非常具有误导性,整个过程非常令人沮丧。
尝试使用 AddHandler 和所有“正常”方法来试图说服 Apache 执行此操作都无济于事。似乎 PUT 只能通过 CGI 调用来处理。