【问题标题】:How to receive a file via HTTP PUT with PHP如何使用 PHP 通过 HTTP PUT 接收文件
【发布时间】:2012-08-13 21:38:57
【问题描述】:

这是困扰我一段时间的事情。我正在构建一个 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 的回答。这是史诗。

【问题讨论】:

  • 嗯?然后你会得到什么错误然后你尝试这样做?
  • 您是如何发送请求的? php://input 仅在多部分请求中不起作用...
  • multipart/formdata 应该生成一个填充的 $_FILES。
  • @Jack 不使用 PUT 方法。
  • @DaveRandom 很不幸 :) 只剩下手动 mime 解码的选项。

标签: php file http put


【解决方案1】:

这是我认为最有用的解决方案。

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

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

【讨论】:

  • 到目前为止最简单的答案。正是我想要的。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

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

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

    基本上我要说的是,接收错误文件的不是您的代码,而是发出请求的代码 - 客户端代码不正确,而不是您在此处显示的代码(尽管 @ 987654327@ 电话是毫无意义的练习)。

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


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

    历史

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

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

    假设您在浏览器的地址栏中输入http://server.domain.tld/path/to/document.ext?key=value。浏览器拆解这个字符串,确定需要连接到server.domain.tld的HTTP服务器,并在/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 request methods

    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 不兼容,因为它不是 idempotent。也就是说,第二个相同的 POST 请求可能会导致服务器状态的进一步变化。这与 REST 的“无状态”约束相矛盾。

    PUT

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

    DELETE

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

    TRACE

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

    CONNECT

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

    【讨论】:

    • 天哪。如果这是真的,那么我就犯了一个大错误,认为 PUT 会像 POST。对于测试,客户端只是服务器上的另一个 php 脚本。我现在通过 GET 更改了发送参数,只将文件内容留在正文中。 - 带有一个额外的标题,与我的问题中描述的完全一样。我相信这个内容头必须存在,否则如何通过同一个请求发送多个文件?关于如何从正文内容中解析文件的任何线索?
    • @Maurice 您正在查看的是内容正文。因为它目前的格式是multipart/* MIME 类型系列的一部分,所以它在正文中也带有“标题”。但是,您要在 PUT 请求中发送的标头应该在请求的实际标头中发送,并且正文应该只包含文件数据。您的客户端 PHP 脚本如何发送请求?通过 cURL?
    • @Maurice 我将根据用户 ID 实现的方式。所以他们会PUT /<user-id>/contact-info.txt 然后你的应用程序可以从 URI 路径中提取用户 ID 并使用它来确定将文件数据存储在哪里 - 在本地文件系统中,在数据库中,等等。我注意到您最初发送的是一张图片,假设那是一张个人资料图片,那么用户会PUT /<user-id>/profile.png - 并且您再次拥有该ID来确定如何处理数据。
    • 要记住的一点是,PUT 并不是特别用户友好,因为浏览器不容易支持它(我不知道 HTML5 是否为它做了一些规定?可能)。你为此提供什么样的接口?一个 HTML 驱动的 GUI 类型接口,还是只是一个需要一些编程知识的 API?
    • @Maurice 不用担心。我已经扩展了上面的答案以供参考 - 我过去遇到过很多人对 PUT 方法有类似的误解,希望现在我可以将它们链接到这里。如果您有任何不明白的地方,请告诉我,因为我想以此作为参考,以(相对)简单的术语解释这一切应该如何工作(理论上)。
    【解决方案4】:

    只需按照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 流上的整个文件并将其保存在本地,然后你可以用它做你想做的事。

    【讨论】:

      【解决方案5】:

      我从未尝试过使用 PUT(GET POST 和 FILES 足以满足我的需要),但这个示例来自 php 文档,因此它可能会对您有所帮助(http://php.net/manual/en/features.file -upload.put-method.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);
      ?>
      

      【讨论】:

      • 这听起来合乎逻辑,除了这也会输出文件头。另外,这也会将 PUTDATA 写入文件。所以就像我预期的那样,这个方法会保存一个损坏的文件。
      • @Maurice:你真的试过了吗?还是你只是在做这个假设?
      • 我刚刚为你试了一下。就像它在代码中所说的那样,它只是将 php://input 流中的所有内容写入一个文件,在我的情况下,它会变得损坏,因为它包含其他数据而不仅仅是文件。要清楚;就我而言,php://input 流包含两件事:1)常规 PUT DATA(如表单数据)和 2)和上传的文件。我会在上面更新我的问题。感谢您的思考!
      • 对不起,如果我不耐烦地过来了。我真的很感谢你一直在思考。过去,文档中的这个例子让我很沮丧,因为我认为他们过于简化了。
      • 原来你是正确的,我的沟通有误。我希望能够通过同一个请求发送多个文件。
      猜你喜欢
      • 2019-07-27
      • 1970-01-01
      • 2010-12-14
      • 2021-10-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-02-22
      • 1970-01-01
      相关资源
      最近更新 更多