web-dev-qa-db-fra.com

Comment recevoir un fichier via HTTP PUT avec PHP

C’est quelque chose qui me dérange depuis un moment. Je construis une API RESTful qui doit recevoir des fichiers à certaines occasions.

Lorsque vous utilisez HTTP POST, nous pouvons lire data from $_POST et files from $_FILES.

Lorsque vous utilisez HTTP GET, nous pouvons lire data from $_GET et files from $_FILES.

Toutefois, lorsque vous utilisez HTTP PUT, autant que je sache, le seul moyen de lire les données consiste à utiliser le php://input stream.

Tout va bien, jusqu'à ce que je veuille envoyer un fichier via HTTP PUT. Maintenant, le flux d'entrée php: // ne fonctionne plus comme prévu, car il contient également un fichier.

Voici comment je lis actuellement les données d'une demande PUT:

(ce qui fonctionne très bien tant qu'aucun fichier n'est posté)

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

parse_str($rawData, $data);

Quand je produis ensuite rawData, il montre

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

Est-ce que quelqu'un sait comment recevoir correctement des fichiers via HTTP PUT, ou comment analyser des fichiers hors du flux d'entrée php: //?

===== UPDATE # 1 =====

J'ai essayé uniquement la méthode ci-dessus, je n'ai pas vraiment la moindre idée de ce que je peux faire d'autre.

En utilisant cette méthode, je n'ai pas d'erreur, à part le fait que je n'obtiens pas le résultat souhaité des données et des fichiers postés.

===== UPDATE # 2 =====

J'envoie cette demande de test à l'aide de Zend_Http_Client, comme suit: (N'a jamais eu de problèmes avec Zend_Http_Client jusqu'à présent)

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

Il s'avère que j'ai fait des hypothèses erronées, principalement que HTTP PUT serait similaire à HTTP POST. Comme vous pouvez le lire ci-dessous, DaveRandom m'a expliqué que HTTP PUT n'est pas conçu pour transférer plusieurs fichiers sur la même demande.

J'ai maintenant déplacé le transfert de formdata du corps à l'url querystring. Le corps contient maintenant le contenu d'un seul fichier.

Pour plus d'informations, lisez la réponse de DaveRandom. C'est épique.

29
Maurice

Les données que vous affichez ne représentent pas un corps de requête PUT valide (enfin, il pourrait, mais j'en doute fortement). Cela montre un corps de requête multipart/form-data - le type MIME utilisé lors du téléchargement de fichiers via HTTP POST via un formulaire HTML.

Les requêtes PUT doivent parfaitement compléter la réponse à une requête GET - elles vous envoient le contenu du fichier dans le corps du message, et rien d'autre.

En gros, ce que je dis, c’est que ce n’est pas votre code pour recevoir le fichier qui est faux, c’est le code qui fait la demande - le code client est incorrect, pas le code que vous affichez ici (bien que l’appel parse_str() soit un exercice inutile).

Si vous expliquez ce qu'est le client (navigateur, script sur un autre serveur, etc.), je peux vous aider à aller plus loin. Dans l'état actuel des choses, la méthode de requête appropriée pour le corps de la requête que vous décrivez est POST, pas PUT.


Prenons un peu de recul par rapport au problème et examinons le protocole HTTP en général, et plus particulièrement le côté des demandes du client. Nous espérons que cela vous aidera à comprendre comment tout cela est censé fonctionner. Tout d’abord, un peu d’histoire (si cela ne vous intéresse pas, n'hésitez pas à sauter cette section).

L'histoire

HTTP a été conçu à l'origine comme un mécanisme permettant de récupérer des documents HTML à partir de serveurs distants. Au début, il ne prenait effectivement en charge que la méthode GET, le client demandant un document par son nom et le serveur le renvoyant au client. La première spécification publique pour HTTP, appelée HTTP 0.9, est apparue en 1991 - et si cela vous intéresse, vous pouvez la lire ici .

La spécification HTTP 1.0 (formalisée en 1996 avec RFC 1945 ) a considérablement étendu les capacités du protocole, en ajoutant les méthodes HEAD et POST. Il n'était pas rétrocompatible avec HTTP 0.9, en raison d'une modification du format de la réponse - un code de réponse a été ajouté, ainsi que de la possibilité d'inclure des métadonnées pour le document renvoyé sous la forme d'en-têtes de format MIME - données clé/valeur paires. HTTP 1.0 a également extrait le protocole du protocole HTML, permettant ainsi le transfert de fichiers et de données dans d'autres formats.

HTTP 1.1, la forme du protocole utilisée presque exclusivement de nos jours, est basée sur HTTP 1.0 et a été conçue pour être rétro-compatible avec les implémentations HTTP 1.0. Il a été normalisé en 1999 avec RFC 2616 . Si vous êtes un développeur travaillant avec HTTP, familiarisez-vous avec ce document - c’est votre bible. Le comprendre pleinement vous donnera un avantage considérable sur vos pairs qui ne le font pas.

Atteindre le point déjà

HTTP fonctionne sur une architecture demande-réponse - le client envoie un message de demande au serveur, le serveur renvoie un message de réponse au client.

Un message de demande comprend une MÉTHODE, un URI et éventuellement un certain nombre de HEADERS. La MÉTHODE de requête correspond au sujet de cette question. C’est donc ce que je vais couvrir de la manière la plus approfondie ici, mais il est d’abord important de comprendre exactement ce que nous voulons dire lorsque nous parlons de l’URI de la requête.

L'URI est l'emplacement sur le serveur de la ressource que nous demandons. En général, il s'agit d'un composant chemin et éventuellement d'un chaîne de requête. Il existe des circonstances dans lesquelles d'autres composants peuvent également être présents, mais pour des raisons de simplicité, nous les ignorerons pour l'instant.

Imaginons que vous tapez http://server.domain.tld/path/to/document.ext?key=value dans la barre d'adresse de votre navigateur. Le navigateur démantèle cette chaîne et détermine qu'il doit se connecter à un serveur HTTP à server.domain.tld et demander le document à /path/to/document.ext?key=value.

La requête HTTP 1.1 générée ressemblera (au minimum) à ceci:

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

La première partie de la demande est le mot GET - il s'agit de la méthode METHOD. La partie suivante est le chemin du fichier que nous demandons - c’est l’URI de la demande. A la fin de cette première ligne se trouve un identifiant indiquant la version du protocole utilisée. Sur la ligne suivante, vous pouvez voir un en-tête au format MIME, appelé Host. HTTP 1.1 exige que l'en-tête Host: soit inclus avec chaque demande. C'est le seul en-tête dont cela est vrai.

L'URI de la demande est divisé en deux parties: tout ce qui se trouve à gauche du point d'interrogation ? est le chemin, tout ce qui se trouve à droite est le chaîne de requête.

Méthodes de requête

RFC 2616 (HTTP/1.1) définit 8 méthodes de requête .

OPTIONS

La méthode OPTIONS est rarement utilisée. Il s'agit d'un mécanisme permettant de déterminer le type de fonctionnalité prise en charge par le serveur avant de tenter de consommer un service fourni par le serveur.De mémoire, le seul endroit couramment utilisé auquel je puisse penser est le cas lorsque vous ouvrez des documents directement dans Microsoft Office via HTTP à partir d'Internet Explorer - Office envoie une demande OPTIONS au serveur pour déterminer s'il prend en charge la méthode PUT pour l'URI spécifique et, le cas échéant, le document s'ouvrira de manière à permettre à l'utilisateur d'enregistrer les modifications apportées au document directement sur le serveur distant. Cette fonctionnalité est étroitement intégrée à ces applications Microsoft spécifiques.

GET.

C’est de loin la méthode la plus répandue au quotidien. Chaque fois que vous chargez un document standard dans votre navigateur Web, il s'agit d'une demande GET.

La méthode GET demande au serveur de retourner un document spécifique. Les seules données qui doivent être transmises au serveur sont les informations nécessaires au serveur pour déterminer le document à renvoyer. Cela peut inclure des informations que le serveur peut utiliser pour générer de manière dynamique le document, qui est envoyé sous la forme d'en-têtes et/ou d'une chaîne de requête dans l'URI de la demande. Pendant que nous sommes sur le sujet - Les cookies sont envoyés dans les en-têtes de requête.

HEAD.

Cette méthode est identique à la méthode GET, à une différence près: le serveur ne renverra pas le document demandé, s'il ne renverra que les en-têtes qui seraient inclus dans la réponse. Ceci est utile pour déterminer, par exemple, si un document particulier existe sans avoir à transférer et traiter le document entier.

POST.

Il s’agit de la deuxième méthode la plus couramment utilisée, et sans doute la plus complexe. Les demandes de méthode POST sont presque exclusivement utilisées pour appeler sur le serveur certaines actions susceptibles de modifier son état.

Une demande POST, contrairement à GET et HEAD, peut (et comprend généralement) certaines données dans le corps du message de demande. Ces données peuvent être dans n'importe quel format, mais il s'agit le plus souvent d'une chaîne de requête (dans le même format que celui indiqué dans l'URI de la requête) ou d'un message en plusieurs parties pouvant communiquer des paires clé/valeur avec des pièces jointes.

De nombreux formulaires HTML utilisent la méthode POST. Pour pouvoir télécharger des fichiers à partir d'un navigateur, vous devez utiliser la méthode POST pour votre formulaire.

La méthode POST est sémantiquement incompatible avec les API RESTful car elle n'est pas idempotent . C'est-à-dire qu'une deuxième demande POST identique peut entraîner un changement supplémentaire de l'état du serveur. Cela contredit la contrainte "sans état" de REST.

PUT.

Cela complète directement GET. Lorsqu'une demande GET indique que le serveur doit renvoyer le document à l'emplacement spécifié par l'URI de la demande dans le corps de la réponse, la méthode PUT indique que le serveur doit stocker les données dans le corps de la demande à l'emplacement spécifié par l'URI de la demande.

DELETE.

Cela indique que le serveur doit détruire le document à l'emplacement indiqué par l'URI de la demande. Très peu d'implémentations de serveurs HTTP avec connexion Internet effectuent une action lorsqu'elles reçoivent une demande DELETE, pour des raisons assez évidentes.

TRACE.

Cela fournit un mécanisme au niveau de la couche d'application permettant aux clients d'inspecter la requête envoyée telle qu'elle se présente au moment où elle atteint le serveur de destination. Cela est surtout utile pour déterminer l'effet que tout serveur proxy entre le client et le serveur de destination peut avoir sur le message de requête.

CONNECT.

HTTP 1.1 réserve le nom d'une méthode CONNECT, mais ne définit ni son utilisation, ni même son objectif. Depuis, certaines implémentations de serveurs proxy ont utilisé la méthode CONNECT pour faciliter le tunneling HTTP.

HTTP 1.1 reserves the name for a CONNECT method, but does not define its usage, or even its purpose. Some proxy server implementations have since used the CONNECT method to facilitate HTTP tunnelling.

41
DaveRandom

Je n'ai jamais essayé d'utiliser PUT (GET POST et FILES suffisaient à mes besoins) mais cet exemple provient de la documentation php, il pourrait donc vous aider (http://php.net/manual/en/features. fichier-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
kjurkovic

Voici la solution que j'ai trouvée la plus utile.

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

$put sera un tableau, comme vous êtes habitué à voir dans $_POST, sauf que maintenant vous pouvez suivre le protocole HTTP REST.

2
Daniel Sikes

Utilisez POST et incluez un en-tête X pour indiquer la méthode actuelle (PUT dans ce cas). C'est généralement ainsi que l'on travaille autour d'un pare-feu qui n'autorise pas les méthodes autres que GET et POST. Déclarez simplement PHP buggy (puisqu'il refuse de gérer les charges PUT multipartes, il IS buggy), et traitez-le comme un pare-feu obsolète/draconien.

Les opinions sur ce que PUT signifie par rapport à GET ne sont que des opinions. Le HTTP ne fait pas une telle exigence. Elle indique simplement «équivalent». Il appartient au concepteur de déterminer ce que signifie «équivalent». Si votre conception accepte un PUT de téléchargement de plusieurs fichiers et génère une représentation "équivalente" pour un GET ultérieur pour la même ressource, c'est très bien, techniquement et philosophiquement, avec les spécifications HTTP. 

1
user4157069

Suivez juste ce qui est écrit dans le 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);
?>

Ceci devrait lire le fichier entier qui se trouve sur le flux PUT et le sauvegarder localement, vous pourrez alors faire ce que vous voulez.

0
Neal