Cómo recibir un archivo vía HTTP PUT con PHP


Esto es algo que me ha estado molestando por un tiempo.. Estoy construyendo una API RESTful que tiene que recibir archivos en algunas ocasiones.

Cuando se usa HTTP POST, podemos leer data from $_POST y files from $_FILES.

Cuando se usa HTTP GET, podemos leer data from $_GET y files from $_FILES.

Sin embargo, cuando se usa HTTP PUT, AFAIK la única forma de leer datos es usar php://input stream.

Todo bien, hasta que quiera enviar un archivo a través de HTTP PUT. Ahora el flujo de entrada php://ya no funciona como se esperaba, ya que tiene un archivo allí también.

Así es como actualmente leo los datos en una solicitud PUT:

(que funciona muy bien siempre y cuando no haya archivos publicados)

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

parse_str($rawData, $data);

Cuando a continuación, la salida RawData, se muestra

-----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

¿Alguien sabe cómo recibir correctamente los archivos a través de HTTP PUT, o cómo analizar los archivos del flujo de entrada php://?

===== ACTUALIZAR #1 =====

He intentado solo el método anterior, realmente no tengo ni idea de lo que puedo hacer else.

No he obtenido errores utilizando este método, además de que no obtengo el resultado deseado de los datos y archivos publicados.

===== ACTUALIZAR #2 =====

Estoy enviando esta solicitud de prueba usando Zend_Http_Client, de la siguiente manera: (no he tenido ningún problema con Zend_Http_Client hasta ahora)

$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' => '...'
));

===== SOLUCIÓN =====

Resulta que hice algunas suposiciones equivocadas, principalmente que HTTP PUT sería similar a HTTP POST. Como puedes leer a continuación, DaveRandom me explicó que HTTP PUT no está destinado a transferir varios archivos en la misma solicitud.

Ahora he movido la transferencia de formdata desde el cuerpo a url querystring. El cuerpo ahora contiene el contenido de un solo archivo.

Para más información, lea la respuesta de DaveRandom. Es épico.

Author: Maurice, 2012-08-17

5 answers

Los datos que muestra no representan un cuerpo de solicitud PUT válido (bueno, podría, pero lo dudo mucho). Lo que muestra es un cuerpo de solicitud multipart/form-data - el tipo MIME utilizado cuando se cargan archivos a través de HTTP POST a través de un formulario HTML.

Las solicitudes PUT deben complementar exactamente la respuesta a una solicitud GET - te envían el contenido del archivo en el cuerpo del mensaje, y nada más.

Esencialmente lo que estoy diciendo es que no es su código para recibir el archivo que está mal, es es el código que está haciendo la solicitud - el código de cliente es incorrecto, no el código que muestra aquí (aunque la llamada parse_str() es un ejercicio inútil).

Si explica qué es el cliente (un navegador, script en otro servidor, etc.), entonces puedo ayudarlo a llevar esto más lejos. Tal como está, el método de solicitud apropiado para el cuerpo de solicitud que representa es POST, no PUT.


Demos un paso atrás del problema y veamos el protocolo HTTP en general, específicamente el protocolo HTTP. lado de la solicitud del cliente-esperemos que esto le ayudará a entender cómo se supone que todo esto funcione. Primero, un poco de historia (si no estás interesado en esto, no dudes en saltarte esta sección).

Historia

HTTP fue diseñado originalmente como un mecanismo para recuperar documentos HTML de servidores remotos. Al principio soportaba solo el método GET, por el cual el cliente solicitaba un documento por nombre y el servidor lo devolvía al cliente. La primera especificación pública para HTTP, etiquetada como HTTP 0.9, apareció en 1991 - y si estás interesado, puedes leerla aquí .

La especificación HTTP 1.0 (formalizada en 1996 con RFC 1945) amplió considerablemente las capacidades del protocolo, añadiendo los métodos HEAD y POST. No era compatible con HTTP 0.9, debido a un cambio en el formato de la respuesta-se añadió un código de respuesta, así como la capacidad de incluir metadatos para el documento devuelto en forma de encabezados de formato MIME-pares de datos clave / valor. HTTP 1.0 también abstrajo el protocolo de HTML, permitiendo la transferencia de archivos y datos en otros formatos.

HTTP 1.1, la forma del protocolo que está casi exclusivamente en uso hoy en día se construye sobre HTTP 1.0 y fue diseñado para ser compatible con versiones anteriores de HTTP 1.0. Fue estandarizado en 1999 con RFC 2616 . Si usted es un desarrollador que trabaja con HTTP, conozca esto documento - es su biblia. Entenderlo completamente le dará una ventaja considerable sobre sus compañeros que no lo hacen.

Llegar al punto de ya

HTTP funciona en una arquitectura de solicitud-respuesta: el cliente envía un mensaje de solicitud al servidor, el servidor devuelve un mensaje de respuesta al cliente.

Un mensaje de solicitud incluye un MÉTODO, un URI y, opcionalmente, un número de ENCABEZADOS. El MÉTODO de solicitud es a lo que se refiere esta pregunta, por lo que es lo que cubriré con más profundidad aquí, pero primero es importante entender exactamente lo que queremos decir cuando hablamos de la URI de solicitud.

El URI es la ubicación en el servidor del recurso que estamos solicitando. En general, esto consiste en un componente path , y opcionalmente una cadena de consulta . Hay circunstancias en las que también pueden estar presentes otros componentes, pero por razones de simplicidad los ignoraremos por ahora.

Vamos a imaginarte escribe http://server.domain.tld/path/to/document.ext?key=value en la barra de direcciones de tu navegador. El navegador desmantela esta cadena, y determina que necesita conectarse a un servidor HTTP en server.domain.tld, y pedir el documento en /path/to/document.ext?key=value.

La solicitud HTTP 1.1 generada se verá (como mínimo) así:

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

La primera parte de la solicitud es la palabra GET - este es el MÉTODO de solicitud. La siguiente parte es la ruta al archivo que estamos solicitando-este es el URI de solicitud. Al final de esta primera línea hay un identificador indicando la versión del protocolo en uso. En la siguiente línea se puede ver un encabezado en formato MIME, llamado Host. HTTP 1.1 exige que el encabezado Host: se incluya con cada solicitud. Este es el único encabezado del que esto es cierto.

El URI de solicitud se divide en dos partes: todo a la izquierda del signo de interrogación ? es la ruta , todo a la derecha es la cadena de consulta .

Métodos de Solicitud

RFC 2616 (HTTP/1.1) define 8 métodos de solicitud.

OPTIONS

El método OPTIONS se usa raramente. Se pretende como un mecanismo para determinar qué tipo de funcionalidad soporta el servidor antes de intentar consumir un servicio que el servidor puede proporcionar.

En la parte superior de mi cabeza, el único lugar en el uso bastante común que se me ocurre donde se usa esto es cuando se abren documentos en Microsoft office directamente a través de HTTP desde Internet Explorer-Office envíe una solicitud de OPCIONES al servidor para determinar si admite el método PUT para el URI específico, y si lo hace, abrirá el documento de una manera que permita al usuario guardar sus cambios en el documento directamente en el servidor remoto. Esta funcionalidad está estrechamente integrada dentro de estas aplicaciones específicas de Microsoft.

GET

Este es de lejos el método más común en el uso diario. Cada vez que cargue un documento normal en su navegador web, será una petición GET.

El método GET solicita que el servidor devuelva un documento específico. Los únicos datos que deben transmitirse al servidor son la información que el servidor requiere para determinar qué documento debe devolverse. Esto puede incluir información que el servidor puede usar para generar dinámicamente el documento, que se envía en forma de encabezados y/o cadena de consulta en el URI de solicitud. Mientras estamos en el tema-Las cookies se envían en la solicitud cabecera.

HEAD

Este método es idéntico al método GET, con una diferencia: el servidor no devolverá el documento solicitado, if solo devolverá las cabeceras que se incluirían en la respuesta. Esto es útil para determinar, por ejemplo, si un documento en particular existe sin tener que transferir y procesar todo el documento.

POST

Este es el segundo método más comúnmente utilizado, y posiblemente el más complejo. Solicitudes de método POST se utilizan casi exclusivamente para invocar algunas acciones en el servidor que pueden cambiar su estado.

Una solicitud POST, a diferencia de GET y HEAD, puede (y generalmente lo hace) incluir algunos datos en el cuerpo del mensaje de solicitud. Estos datos pueden estar en cualquier formato, pero lo más común es una cadena de consulta (en el mismo formato que aparecería en el URI de solicitud) o un mensaje de varias partes que puede comunicar pares clave/valor junto con archivos adjuntos.

Muchos formularios HTML usan el método POST. En orden para subir archivos desde un navegador, necesitarás usar el método POST para tu formulario.

El método POST es semánticamente incompatible con las API RESTful porque no es idempotente. Es decir, una segunda solicitud POST idéntica puede resultar en un cambio adicional en el estado del servidor. Esto contradice la restricción "apátrida" del DESCANSO.

PUT

Esto complementa directamente a GET. Donde una solicitud GET indica que el servidor debe devolver el documento en la ubicación especificada por el URI de solicitud en el cuerpo de respuesta, el método PUT indica que el servidor debe almacenar los datos en el cuerpo de la solicitud en la ubicación especificada por el URI de solicitud.

DELETE

Esto indica que el servidor debe destruir el documento en la ubicación indicada por el URI de solicitud. Muy pocas implementaciones de servidor HTTP enfrentadas a Internet realizarán cualquier acción cuando reciban una solicitud de ELIMINACIÓN, por bastante obvio motivo.

TRACE

Esto proporciona un mecanismo de nivel de capa de aplicación para permitir a los clientes inspeccionar la solicitud que ha enviado tal y como se ve en el momento en que llega al servidor de destino. Esto es principalmente útil para determinar el efecto que cualquier servidor proxy entre el cliente y el servidor de destino puede tener en el mensaje de solicitud.

CONNECT

HTTP 1.1 reserva el nombre de un método CONNECT, pero no define su uso, ni siquiera su propósito. Algunos desde entonces, las implementaciones de servidores proxy han utilizado el método CONNECT para facilitar la tunelización HTTP.

 41
Author: DaveRandom,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-17 15:43:32

Nunca he intentado usar PUT (GET POST y ARCHIVOS eran suficientes para mis necesidades) pero este ejemplo es de los documentos php por lo que podría ayudarle (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);
?>
 6
Author: kjurkovic,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-17 12:30:17

Aquí está la solución que me pareció más útil.

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

$put será una matriz, al igual que está acostumbrado a ver en $_POST, excepto que ahora puede seguir el verdadero protocolo HTTP REST.

 2
Author: Daniel Sikes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-19 04:16:01

Solo sigue lo que dice en el 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);
?>

Este debería leer todo el archivo que está en el flujo PUT y guardarlo localmente, entonces podría hacer lo que quiera con él.

 1
Author: Neal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-17 13:02:36

Use POST e incluya un encabezado X para indicar el método real (PUESTO en este caso). Por lo general, así es como se trabaja alrededor de un firewall que no permite otros métodos que no sean GET y POST. Simplemente declare PHP buggy (ya que se niega a manejar cargas útiles PUT de varias partes, es buggy), y trátelo como lo haría con un cortafuegos obsoleto/draconiano.

Las opiniones sobre qué significa PUT en relación con GET son solo eso, opiniones. El HTTP no hace tal requisito. Simplemente dice "equivalente" .. depende del diseñador determinar qué significa "equivalente". Si su diseño puede aceptar una carga de archivos múltiples PUT y producir una representación 'equivalente' para una posterior GET para el mismo recurso, eso está bien y excelente, tanto técnica como filosóficamente, con las especificaciones HTTP.

 1
Author: user4157069,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-18 16:28:35