32

这是困扰我一段时间的事情。我正在构建一个 RESTful API,它必须在某些情况下接收文件。

使用时HTTP POST,我们可以读取data from $_POSTfiles from $_FILES

使用时HTTP GET,我们可以读取data from $_GETfiles from $_FILES

但是,在使用 时HTTP PUT,AFAIK 读取数据的唯一方法是使用php://input stream.

一切都很好,直到我想通过 HTTP PUT 发送文件。现在 php://input 流不再按预期工作,因为它也有一个文件。

以下是我目前在 PUT 请求中读取数据的方式:

(只要没有发布文件就可以很好地工作)

$handle  = fopen('php://input', 'r');
$rawData = '';
while ($chunk = fread($handle, 1024)) {
    $rawData .= $chunk;
}

parse_str($rawData, $data);

当我然后输出 rawData 时,它显示

-----ZENDHTTPCLIENT-44cf242ea3173cfa0b97f80c68608c4c
Content-Disposition: form-data; name="image_01"; filename="lorem-ipsum.png"
Content-Type: image/png; charset=binary

�PNG
���...etc etc...
���,
-----ZENDHTTPCLIENT-8e4c65a6678d3ef287a07eb1da6a5380
Content-Disposition: form-data; name="testkey"

testvalue
-----ZENDHTTPCLIENT-8e4c65a6678d3ef287a07eb1da6a5380
Content-Disposition: form-data; name="otherkey"

othervalue

有谁知道如何通过 HTTP PUT 正确接收文件,或者如何从 php://input 流中解析文件?

===== 更新#1 =====

我只尝试了上述方法,不知道我还能做什么。

使用这种方法我没有得到任何错误,除了我没有得到发布的数据和文件的预期结果。

===== 更新 #2 =====

我正在使用 Zend_Http_Client 发送这个测试请求,如下所示:(到目前为止 Zend_Http_Client 没有任何问题)

$client = new Zend_Http_Client();
$client->setConfig(array(
    'strict'       => false,
    'maxredirects' => 0,
    'timeout'      => 30)
);
$client->setUri( 'http://...' );
$client->setMethod(Zend_Http_Client::PUT);
$client->setFileUpload( dirname(__FILE__) . '/files/lorem-ipsum.png', 'image_01');
$client->setParameterPost(array('testkey' => 'testvalue', 'otherkey' => 'othervalue');
$client->setHeaders(array(
    'api_key'    => '...',
    'identity'   => '...',
    'credential' => '...'
));

===== 解决方案 =====

结果我做了一些错误的假设,主要是 HTTP PUT 类似于 HTTP POST。正如您在下面看到的,DaveRandom 向我解释说 HTTP PUT 并不意味着在同一个请求上传输多个文件。

我现在已经将表单数据从正文转移到 url 查询字符串。正文现在包含单个文件的内容。

有关更多信息,请阅读 DaveRandom 的答案。这是史诗。

4

5 回答 5

44

您显示的数据并未描述有效的 PUT 请求正文(嗯,它可以,但我非常怀疑)。它显示的是multipart/form-data请求正文 - 通过 HTML 表单通过 HTTP POST 上传文件时使用的 MIME 类型。

PUT 请求应该完全补充对 GET 请求的响应——它们向您发送消息正文中的文件内容,仅此而已。

本质上,我要说的是,接收错误文件的不是您的代码,而是发出请求的代码-客户端代码不正确,而不是您在此处显示的代码(尽管parse_str()调用毫无意义锻炼)。

如果您解释客户端是什么(浏览器、其他服务器上的脚本等),那么我可以帮助您更进一步。实际上,您描述的请求正文的适当请求方法是 POST,而不是 PUT。


让我们从这个问题退后一步,看看一般的 HTTP 协议 - 特别是客户端请求端 - 希望这将帮助您了解所有这些应该如何工作。首先,有一点历史(如果您对此不感兴趣,请随意跳过本节)。

历史

HTTP 最初被设计为一种从远程服务器检索 HTML 文档的机制。起初,它只有效地支持 GET 方法,即客户端通过名称请求文档,服务器将其返回给客户端。HTTP 的第一个公共规范,标记为 HTTP 0.9,出现在 1991 年 - 如果您有兴趣,可以在此处阅读。

HTTP 1.0 规范(在 1996 年与RFC 1945一起正式制定)大大扩展了协议的功能,添加了 HEAD 和 POST 方法。由于响应格式的变化,它不向后兼容 HTTP 0.9 - 添加了响应代码,以及以 MIME 格式标头的形式包含返回文档的元数据的能力 - 键/值数据对。HTTP 1.0 还从 HTML 中抽象出协议,允许以其他格式传输文件和数据。

HTTP 1.1 是当今几乎完全使用的协议形式,它建立在 HTTP 1.0 之上,旨在向后兼容 HTTP 1.0 实现。它于 1999 年通过RFC 2616标准化。如果您是使用 HTTP 的开发人员,请了解此文档 - 它是您的圣经。完全理解它会让你比不理解它的同龄人有相当大的优势。

已经进入正题

HTTP 工作在请求-响应架构上——客户端向服务器发送请求消息,服务器向客户端返回响应消息。

请求消息包括一个 METHOD、一个 URI 和可选的一些 HEADERS。请求方法是这个问题所涉及的,所以我将在这里最深入地介绍它——但首先,当我们谈论请求 URI 时,准确理解我们的意思很重要。

URI 是我们请求的资源在服务器上的位置。通常,这由一个路径组件和一个可选的查询字符串组成。在某些情况下也可能存在其他组件,但为了简单起见,我们现在将忽略它们。

假设您http://server.domain.tld/path/to/document.ext?key=value在浏览器的地址栏中键入内容。浏览器拆解这个字符串,并确定它需要在 连接到 HTTP 服务器server.domain.tld,并在 请求文件/path/to/document.ext?key=value

生成的 HTTP 1.1 请求将(至少)如下所示:

GET /path/to/document.ext?key=value HTTP/1.1
Host: server.domain.tld

请求的第一部分是单词GET——这是请求方法。下一部分是我们请求的文件的路径——这是请求 URI。在第一行的末尾是一个标识符,指示正在使用的协议版本。在下一行中,您可以看到一个 MIME 格式的标题,称为Host. HTTP 1.1 要求Host:标头包含在每个请求中。这是唯一正确的标题。

请求 URI 分为两部分 - 问号左边的所有内容?都是路径,它右边的所有内容都是查询字符串

请求方法

RFC 2616 (HTTP/1.1) 定义了8 种请求方法

OPTIONS

OPTIONS 方法很少使用。它旨在作为一种机制,用于在尝试使用服务器可能提供的服务之前确定服务器支持哪种功能。

在我的脑海中,我能想到的唯一一个相当常见的用法是在 Microsoft Office 中直接通过 HTTP 从 Internet Explorer 打开文档时 - Office 将向服务器发送一个 OPTIONS 请求以确定它是否支持特定 URI 的 PUT 方法,如果支持,它将以允许用户将他们对文档的更改直接保存回远程服务器的方式打开文档。此功能紧密集成在这些特定的 Microsoft 应用程序中。

GET

这是迄今为止在日常使用中最常见的方法。每次您在 Web 浏览器中加载常规文档时,它都会是一个 GET 请求。

GET 方法请求服务器返回一个特定的文档。应该传输到服务器的唯一数据是服务器需要确定应该返回哪个文档的信息。这可以包括服务器可以用来动态生成文档的信息,该文档以请求 URI 中的标题和/或查询字符串的形式发送。当我们讨论这个主题时 - Cookie 是在请求​​标头中发送的。

HEAD

此方法与 GET 方法相同,但有一个区别 - 服务器不会返回请求的文档,如果只返回将包含在响应中的标头。例如,这对于确定是否存在特定文档而不必传输和处理整个文档很有用。

POST

这是第二种最常用的方法,可以说是最复杂的方法。POST 方法请求几乎专门用于调用服务器上可能更改其状态的某些操作。

与 GET 和 HEAD 不同,POST 请求可以(并且通常确实)在请求消息的正文中包含一些数据。此数据可以是任何格式,但最常见的是查询字符串(与请求 URI 中显示的格式相同)或多部分消息,可以与文件附件一起传递键/值对。

许多 HTML 表单使用 POST 方法。为了从浏览器上传文件,您需要对表单使用 POST 方法。

POST 方法在语义上与 RESTful API 不兼容,因为它不是幂等的。也就是说,第二个相同的 POST 请求可能会导致服务器状态的进一步变化。这与 REST 的“无状态”约束相矛盾。

PUT

这直接补充了 GET。如果 GET 请求指示服务器应在响应正文中的请求 URI 指定的位置返回文档,则 PUT 方法指示服务器应将数据存储在请求正文中由请求 URI 指定的位置。

DELETE

这表明服务器应该在请求 URI 指示的位置销毁文档。由于相当明显的原因,很少有面向 Internet 的 HTTP 服务器实现会在收到 DELETE 请求时执行任何操作。

TRACE

这提供了一种应用层级别的机制,允许客户端在请求到达目标服务器时检查它发送的请求。这对于确定客户端和目标服务器之间的任何代理服务器可能对请求消息产生的影响非常有用。

CONNECT

HTTP 1.1 保留了 CONNECT 方法的名称,但没有定义它的用法,甚至没有定义它的用途。一些代理服务器实现已经使用 CONNECT 方法来促进 HTTP 隧道。

于 2012-08-17T13:00:14.777 回答
6

我从未尝试过使用 PUT(GET POST 和 FILES 足以满足我的需要),但这个示例来自 php 文档,因此它可能对您有所帮助(http://php.net/manual/en/features.file-upload.放置方法.php):

<?php
/* PUT data comes in on the stdin stream */
$putdata = fopen("php://input", "r");

/* Open a file for writing */
$fp = fopen("myputfile.ext", "w");

/* Read the data 1 KB at a time
   and write to the file */
while ($data = fread($putdata, 1024))
  fwrite($fp, $data);

/* Close the streams */
fclose($fp);
fclose($putdata);
?>
于 2012-08-17T12:30:17.133 回答
3

这是我发现最有用的解决方案。

$put = array(); parse_str(file_get_contents('php://input'), $put);

$put将是一个数组,就像您以前在 中看到的一样$_POST,除了现在您可以遵循真正的 REST HTTP 协议。

于 2016-02-19T04:16:01.010 回答
1

使用 POST 并包含一个 X- 标头来指示实际方法(在本例中为 PUT)。通常这是绕过防火墙的工作方式,防火墙不允许使用 GET 和 POST 以外的方法。只需声明 PHP 错误(因为它拒绝处理多部分 PUT 有效负载,所以它是错误的),然后像对待过时/严苛的防火墙一样对待它。

关于 PUT 相对于 GET 的含义的意见只是意见。HTTP 没有这样的要求。它只是声明“等效”.. 由设计人员确定“等效”的含义。如果您的设计可以接受多文件上传 PUT 并为同一资源的后续 GET 生成“等效”表示,那么无论从技术上还是从哲学上来说,使用 HTTP 规范都很好。

于 2014-10-18T16:28:35.233 回答
0

只需按照DOC中的说明进行操作即可:

<?php
/* PUT data comes in on the stdin stream */
$putdata = fopen("php://input", "r");

/* Open a file for writing */
$fp = fopen("myputfile.ext", "w");

/* Read the data 1 KB at a time
   and write to the file */
while ($data = fread($putdata, 1024))
  fwrite($fp, $data);

/* Close the streams */
fclose($fp);
fclose($putdata);
?>

应该读取 PUT 流上的整个文件并将其保存在本地,然后你可以用它做你想做的事。

于 2012-08-17T12:30:39.457 回答