简体   繁体   English

如何使用PHP通过HTTP PUT接收文件

[英]How to receive a file via HTTP PUT with PHP

This is something that has been bugging me for a while.. I'm building of a RESTful API that has to receive files on some occasions. 这一直困扰着我一段时间......我正在构建一个RESTful API,在某些情况下必须接收文件。

When using HTTP POST , we can read data from $_POST and files from $_FILES . 使用HTTP POST ,我们可以data from $_POST读取data from $_POST files from $_FILES读取files from $_FILES

When using HTTP GET , we can read data from $_GET and files from $_FILES . 使用HTTP GET ,我们可以data from $_GET读取data from $_GET files from $_FILES读取files from $_FILES

However, when using HTTP PUT , AFAIK the only way to read data is to use the php://input stream . 但是,在使用HTTP PUT ,AFAIK读取数据的唯一方法是使用php://input stream

All good and well, untill I want to send a file over HTTP PUT. 一切都很好,直到我想通过HTTP PUT发送文件。 Now the php://input stream doesn't work as expected anymore, since it has a file in there as well. 现在php://输入流不再按预期工作了,因为它也有一个文件。

Here's how I currently read data on a PUT request: 这是我目前如何读取PUT请求的数据:

(which works great as long as there are no files posted) (只要没有发布文件,它就能很好用)

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

parse_str($rawData, $data);

When I then output rawData, it shows 当我输出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

Does anyone know how to properly receive files over HTTP PUT, or how to parse files out of the php://input stream? 有谁知道如何通过HTTP PUT正确接收文件,或者如何解析php://输入流中的文件?

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

I have tried only the above method, don't really have a clue as to what I can do else. 我只尝试过上面的方法,不知道我能做些什么。

I have gotten no errors using this method, besides that I don't get the desired result of the posted data and files. 我使用这种方法没有错误,除了我没有得到所发布的数据和文件的预期结果。

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

I'm sending this test request using Zend_Http_Client, as follows: (haven't had any problems with Zend_Http_Client so far) 我正在使用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' => '...'
));

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

Turns out I made some wrong assumptions, mainly that HTTP PUT would be similar to HTTP POST. 结果我做了一些错误的假设,主要是HTTP PUT与HTTP POST类似。 As you can read below, DaveRandom explained to me that HTTP PUT is not meant for transferring multiple files on the same request. 如下所示,DaveRandom向我解释说,HTTP PUT不适用于在同一请求中传输多个文件。

I have now moved the transferring of formdata from the body to url querystring. 我现在已经将formdata从body转移到url querystring。 The body now holds the contents of a single file. 正文现在保存单个文件的内容。

For more information, read DaveRandom's answer. 有关更多信息,请阅读DaveRandom的回答。 It's epic. 这是史诗。

The data you show does not depict a valid PUT request body (well, it could , but I highly doubt it). 您显示的数据并未描述有效的PUT请求正文(嗯,它可以 ,但我非常怀疑)。 What it shows is a multipart/form-data request body - the MIME type used when uploading files via HTTP POST through an HTML form. 它显示的是一个multipart/form-data请求体 - 通过HTTP表单通过HTTP POST上传文件时使用的MIME类型。

PUT requests should exactly compliment the response to a GET request - they send you the file contents in the message body, and nothing else. PUT请求应该完全补充对GET请求的响应 - 它们会向您发送消息体中的文件内容,而不是其他内容。

Essentially what I'm saying is that it is not your code to receive the file that is wrong, it is the code that is making the request - the client code is incorrect, not the code you show here (although the parse_str() call is a pointless exercise). 基本上我所说的是,接收错误的文件不是你的代码,它是发出请求的代码 - 客户端代码不正确,而不是你在这里显示的代码(尽管parse_str()调用是一种毫无意义的运动)。

If you explain what the client is (a browser, script on other server, etc) then I can help you take this further. 如果您解释客户端是什么(浏览器,其他服务器上的脚本等),那么我可以帮助您进一步。 As it is, the appropriate request method for the request body that you depict is POST, not PUT. 实际上,您描述的请求主体的适当请求方法是POST,而不是PUT。


Let's take a step back from the problem, and look at the HTTP protocol in general - specifically the client request side - hopefully this will help you understand how all of this is supposed to work. 让我们从问题中退后一步,看看一般的HTTP协议 - 特别是客户端请求方 - 希望这将有助于您了解所有这些应该如何工作。 First, a little history (if you're not interested in this, feel free to skip this section). 首先,有一点历史(如果你对此不感兴趣,请随意跳过本节)。

History 历史

HTTP was originally designed as a mechanism for retrieving HTML documents from remote servers. HTTP最初被设计为从远程服务器检索HTML文档的机制。 At first it effectively supported only the GET method, whereby the client would request a document by name and the server would return it to the client. 起初它只有效地支持GET方法,客户端会按名称请求文档,服务器会将其返回给客户端。 The first public specification for HTTP, labelled as HTTP 0.9, appeared in 1991 - and if you're interested, you can read it here . HTTP的第一个公共规范,标记为HTTP 0.9,出现在1991年 - 如果您有兴趣,可以在这里阅读。

The HTTP 1.0 specification (formalised in 1996 with RFC 1945 ) expanded the capabilities of the protocol considerably, adding the HEAD and POST methods. HTTP 1.0规范(1996年使用RFC 1945形式化)大大扩展了协议的功能,添加了HEAD和POST方法。 It was not backwards compatible with HTTP 0.9, due to a change in the format of the response - a response code was added, as well as the ability to include metadata for the returned document in the form of MIME format headers - key/value data pairs. 它没有向后兼容HTTP 0.9,因为响应格式发生了变化 - 添加了响应代码,以及以MIME格式标题的形式包含返回文档的元数据的能力 - 键/值数据对。 HTTP 1.0 also abstracted the protocol from HTML, allowing for the transfer of files and data in other formats. HTTP 1.0还从HTML中抽象出协议,允许以其他格式传输文件和数据。

HTTP 1.1, the form of the protocol that is almost exclusively in use today is built on top of HTTP 1.0 and was designed to be backwards compatible with HTTP 1.0 implementations. HTTP 1.1是目前几乎全部使用的协议形式,它建立在HTTP 1.0之上,旨在向后兼容HTTP 1.0实现。 It was standardised in 1999 with RFC 2616 . 它在1999年通过RFC 2616标准化。 If you are a developer working with HTTP, get to know this document - it is your bible. 如果您是使用HTTP的开发人员,请了解此文档 - 这是您的圣经。 Understanding it fully will give you a considerable advantage over your peers who do not. 充分理解它会比没有这样做的同行更具优势。

Get to the point already 已经达到了目的

HTTP works on a request-response architecture - the client sends a request message to the server, the server returns a response message to the client. HTTP在请求 - 响应体系结构上工作 - 客户端向服务器发送请求消息,服务器向客户端返回响应消息。

A request message includes a METHOD, a URI and optionally, a number of HEADERS. 请求消息包括METHOD,URI和可选的多个HEADERS。 The request METHOD is what this question relates to, so it is what I will cover in the most depth here - but first it is important to understand exactly what we mean when we talk about the request URI. 请求METHOD是这个问题所涉及的,所以我将在这里深入讨论 - 但首先,在我们讨论请求URI时,确切了解我们的意思是很重要的。

The URI is the location on the server of the resource we are requesting. URI是我们请求的资源的服务器上的位置。 In general, this consists of a path component, and optionally a query string . 通常,它由路径组件和可选的查询字符串组成 There are circumstances where other components may be present as well, but for the purposes of simplicity we shall ignore them for now. 在某些情况下,也可能存在其他组件,但为了简单起见,我们暂时忽略它们。

Let's imagine you type http://server.domain.tld/path/to/document.ext?key=value into the address bar of your browser. 让我们假设您在浏览器的地址栏中键入http://server.domain.tld/path/to/document.ext?key=value The browser dismantles this string, and determines that it needs to connect to an HTTP server at server.domain.tld , and ask for the document at /path/to/document.ext?key=value . 浏览器会解除此字符串,并确定它需要连接到server.domain.tld上的HTTP服务器,并在/path/to/document.ext?key=value请求该文档。

The generated HTTP 1.1 request will look (at a minimum) like this: 生成的HTTP 1.1请求将(至少)看起来像这样:

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

The first part of the request is the word GET - this is the request METHOD. 请求的第一部分是GET一词 - 这是请求METHOD。 The next part is the path to the file we are requesting - this is the request URI. 下一部分是我们请求的文件的路径 - 这是请求URI。 At the end of this first line is an identifier indicating the protocol version in use. 在第一行的末尾是指示正在使用的协议版本的标识符。 On the following line you can see a header in MIME format, called Host . 在以下行中,您可以看到MIME格式的标题,称为Host HTTP 1.1 mandates that the Host: header be included with every request. HTTP 1.1要求每个请求都包含Host:头。 This is the only header of which this is true. 这是唯一的标题。

The request URI is broken into two parts - everything to the left of the question mark ? 请求URI分为两部分 - 问号左侧的所有内容? is the path , everything to the right of it is the query string . 路径 ,它右边的一切都是查询字符串

Request Methods 请求方法

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

OPTIONS

The OPTIONS method is rarely used. OPTIONS方法很少使用。 It is intended as a mechanism for determining what kind of functionality the server supports before attempting to consume a service the server may provide. 它旨在作为一种机制,用于在尝试使用服务器可能提供的服务之前确定服务器支持哪种功能。

Off the top of my head, the only place in fairly common usage that I can think of where this is used is when opening documents in Microsoft office directly over HTTP from Internet Explorer - Office will send an OPTIONS request to the server to determine if it supports the PUT method for the specific URI, and if it does it will open the document in a way that allows the user to save their changes to the document directly back to the remote server. 在我的头脑中,我能想到使用它的唯一常见用法是在Microsoft Office中直接通过HTTP从Internet Explorer打开文档 - Office会向服务器发送OPTIONS请求以确定是否支持特定URI的PUT方法,如果支持,它将以允许用户将对文档的更改直接保存回远程服务器的方式打开文档。 This functionality is tightly integrated within these specific Microsoft applications. 此功能紧密集成在这些特定的Microsoft应用程序中。

GET

This is by far and away the most common method in every day usage. 这是迄今为止每天使用中最常用的方法。 Every time you load a regular document in your web browser it will be a GET request. 每次在Web浏览器中加载常规文档时,都将是GET请求。

The GET method requests that the server return a specific document. GET方法请求服务器返回特定文档。 The only data that should be transmitted to the server is information that the server requires to determine which document should be returned. 应该传输到服务器的唯一数据是服务器确定应返回哪个文档所需的信息。 This can include information that the server can use to dynamically generate the document, which is sent in the form of headers and/or query string in the request URI. 这可以包括服务器可以用来动态生成文档的信息,该信息以请求URI中的头和/或查询字符串的形式发送。 While we're on the subject - Cookies are sent in the request headers. 我们讨论的主题是 - 在请求标头中发送Cookie。

HEAD

This method is identical to the GET method, with one difference - the server will not return the requested document, if will only return the headers that would be included in the response. 此方法与GET方法相同,但有一点不同 - 服务器将不返回请求的文档,如果只返回将包含在响应中的标头。 This is useful for determining, for example, if a particular document exists without having to transfer and process the entire document. 例如,这对于确定特定文档是否存在而不必传输和处理整个文档是有用的。

POST

This is the second most commonly used method, and arguably the most complex. 这是第二种最常用的方法,可以说是最复杂的方法。 POST method requests are almost exclusively used to invoke some actions on the server that may change its state. POST方法请求几乎专门用于调用服务器上可能更改其状态的某些操作。

A POST request, unlike GET and HEAD, can (and usually does) include some data in the body of the request message. 与GET和HEAD不同,POST请求可以(并且通常确实)在请求消息的主体中包含一些数据。 This data can be in any format, but most commonly it is a query string (in the same format as it would appear in the request URI) or a multipart message that can communicate key/value pairs along with file attachments. 此数据可以是任何格式,但最常见的是查询字符串(格式与请求URI中显示的格式相同)或多部分消息,可以与文件附件一起传递键/值对。

Many HTML forms use the POST method. 许多HTML表单使用POST方法。 In order to upload files from a browser, you would need to use the POST method for your form. 要从浏览器上传文件,您需要为表单使用POST方法。

The POST method is semantically incompatible with RESTful APIs because it is not idempotent . POST方法在语义上与RESTful API不兼容,因为它不是幂等的 That is to say, a second identical POST request may result in a further change to the state of the server. 也就是说,第二个相同的POST请求可能导致对服务器状态的进一步改变。 This contradicts the "stateless" constraint of REST. 这与REST的“无状态”约束相矛盾。

PUT

This directly complements GET. 这直接补充了GET。 Where a GET requests indicates that the server should return the document at the location specified by the request URI in the response body, the PUT method indicates that the server should store the data in the request body at the location specified by the request URI. 如果GET请求指示服务器应在响应主体中的请求URI指定的位置返回文档,则PUT方法指示服务器应将数据存储在请求URI指定的位置的请求主体中。

DELETE

This indicates that the server should destroy the document at the location indicated by the request URI. 这表明服务器应该在请求URI指示的位置销毁文档。 Very few internet facing HTTP server implementations will perform any action when they receive a DELETE request, for fairly obvious reasons. 很少有面向互联网的HTTP服务器实现在收到DELETE请求时会执行任何操作,原因相当明显。

TRACE

This provides an application-layer level mechanism to allow clients to inspect the request it has sent as it looks by the time it reaches the destination server. 这提供了一种应用程序层级机制,允许客户端在到达目标服务器时检查它发送的请求。 This is mostly useful for determining the effect that any proxy servers between the client and the destination server may be having on the request message. 这对于确定客户端和目标服务器之间的任何代理服务器可能对请求消息产生的影响非常有用。

CONNECT

HTTP 1.1 reserves the name for a CONNECT method, but does not define its usage, or even its purpose. HTTP 1.1保留CONNECT方法的名称,但不定义其用法,甚至不限定其用途。 Some proxy server implementations have since used the CONNECT method to facilitate HTTP tunnelling. 从那时起,一些代理服务器实现使用CONNECT方法来促进HTTP隧道。

I've never tried using PUT (GET POST and FILES were sufficient for my needs) but this example is from the php docs so it might help you (http://php.net/manual/en/features.file-upload.put-method.php): 我从来没有尝试过使用PUT(GET POST和FILES足以满足我的需求),但这个例子来自php文档,所以它可以帮到你(http://php.net/manual/en/features.file-upload。把-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);
?>

Here is the solution that I found to be the most useful. 这是我发现最有用的解决方案。

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

$put will be an array, just like you are used to seeing in $_POST , except now you can follow true REST HTTP protocol. $put将是一个数组,就像你以前在$_POST看到的一样,除了现在你可以遵循真正的REST HTTP协议。

Use POST and include an X- header to indicate the actual method (PUT in this case). 使用POST并包含一个X-header来指示实际方法(在这种情况下为PUT)。 Usually this is how one works around a firewall which does not allow methods other than GET and POST. 通常这就是围绕防火墙工作的方式,它不允许GET和POST以外的方法。 Simply declare PHP buggy (since it refuses to handle multipart PUT payloads, it IS buggy), and treat it as you would an outdated/draconian firewall. 简单地声明PHP错误(因为它拒绝处理多部分PUT有效载荷,它是错误的),并将其视为过时/严苛的防火墙。

The opinions as to what PUT means in relation to GET are just that, opinions. 关于PUT与GET相关的意见只是意见。 The HTTP makes no such requirement. HTTP没有这样的要求。 It simply states 'equivalent' .. it is up to the designer to determine what 'equivalent' means. 它简单地说明了“等效”......设计师需要确定“等效”的含义。 If your design can accept a multi-file upload PUT and produce an 'equivalent' representation for a subsequent GET for the same resource, that's just fine and dandy, both technically and philosophically, with the HTTP specifications. 如果您的设计可以接受多文件上传PUT并为同一资源的后续GET生成“等效”表示,那么在技术上和哲学上都可以使用HTTP规范。

Just follow what it says in the DOC : 按照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);
?>

This should read the whole file that is on the PUT stream and save it locally, then you could do what you want with it. 应该读取PUT流上的整个文件并将其保存在本地,然后你可以用它做你想做的事情。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM