POSTER des données composites
Dans la vie de tout programmeur, des problèmes surviennent qui attrapent une personne. Je n’aime pas la solution standard et c’est tout! Et parfois, il arrive que les solutions standard ne fonctionnent pas pour une raison quelconque. Certaines personnes contournent ces tâches, tandis que d'autres aiment les résoudre. Vous pouvez même dire qu'ils les trouvent eux-mêmes. L'une de ces tâches consiste à envoyer un fichier ou plusieurs fichiers à l'aide de la méthode POST.
Certains diront probablement que cette tâche n'est pas du tout une tâche. Après tout, il existe une merveilleuse bibliothèque CURL qui est assez simple et résout ce problème facilement! Mais ne soyez pas pressé. Oui, CURL est une bibliothèque puissante, oui elle charge des fichiers, mais ... Comme vous le savez, elle a une petite fonctionnalité - le fichier doit être placé sur votre disque dur!
Imaginons maintenant la situation suivante, vous générez dynamiquement un fichier ou il est déjà en mémoire et vous devez l'envoyer en utilisant la méthode POST à ​​un serveur Web distant. Que se passe-t-il alors? Devez-vous l'enregistrer avant de l'envoyer? C'est exactement ce que feraient 90% des programmeurs. Pourquoi rechercher des problèmes inutiles si la solution se trouve à la surface? Mais nous ne sommes pas avec vous de ces 90%! Nous sommes meilleurs, nous pouvons résoudre n'importe quel problème. Pourquoi avons-nous besoin d'une action supplémentaire? Premièrement, il utilise le système de fichiers pas rapide du disque dur. Deuxièmement, il se peut que nous n’ayons pas accès au système de fichiers ou que trop peu d’espace y soit alloué.
Comment pouvons-nous alors résoudre ce problème? Pour ce faire, vous devez examiner comment les données sont réellement transmises par la méthode POST. La seule solution est de transférer le fichier en tant que requête composée en utilisantmultipart / form-data . Cette technique est bien documentée dans la RFC7578 . Voyons à quoi ressemblera le corps d'une requête POST multipart / form-data:
POST /form.html HTTP / 1.1 Hôte: server.com Référent: http://server.com/form.html Agent utilisateur: Mozilla Content-Type: multipart / form-data; frontière = ------------- 573cf973d5228 Contenu-Longueur: 288 Connexion: keep-alive Keep-Alive: 300 (ligne vide) (préambule manquant) --------------- 573cf973d5228 Content-Disposition: données de formulaire; name = "champ" texte --------------- 573cf973d5228 Content-Disposition: données de formulaire; nom = "fichier"; filename = "sample.txt" Content-Type: texte / brut Fichier de contenu --------------- 573cf973d5228--
Notre corps se compose de deux parties, dans la première partie nous passons la valeur du formulaire field name = "field" égale à : texte . Dans la deuxième partie, nous passons le champ name = "file" avec le contenu du fichier filename = "sample.txt": Content file . Dans l'en-tête, nous spécifions le format du contenu de la requête POST - Content-Type: multipart / form-data , la chaîne de séparation des parties: boundary = ------------- 573cf973d5228 et la longueur du message - Content-Length: 288 .
Il reste, en fait, à écrire un programme qui implémente cette méthode. Puisque nous sommes des gens intelligents et n'écrivons pas cent fois la même chose dans différents projets, nous allons tout organiser sous la forme d'une classe qui implémente cette méthode. De plus, développons-le pour différentes options d'envoi de fichiers et d'éléments de formulaire simples. Et pour distinguer la présence d'un fichier parmi le tableau de données POST, créons un fichier séparé - un conteneur avec le contenu du fichier et ses données (nom et extension). Ainsi, cela ressemblera à ceci:
<pre>
class oFile
{
private $name;
private $mime;
private $content;
public function __construct($name, $mime=null, $content=null)
{
// , $content=null, $name -
if(is_null($content))
{
// (, )
$info = pathinfo($name);
//
if(!empty($info['basename']) && is_readable($name))
{
$this->name = $info['basename'];
// MIME
$this->mime = mime_content_type($name);
//
$content = file_get_contents($name);
//
if($content!==false) $this->content = $content;
else throw new Exception('Don`t get content - "'.$name.'"');
} else throw new Exception('Error param');
} else
{
//
$this->name = $name;
// MIME
if(is_null($mime)) $mime = mime_content_type($name);
// MIME
$this->mime = $mime;
//
$this->content = $content;
};
}
//
public function Name() { return $this->name; }
// MIME
public function Mime() { return $this->mime; }
//
public function Content() { return $this->content; }
};
</pre>
Maintenant, la classe elle-mĂŞme pour former le corps de multipart / form-data pour la requĂŞte POST:
<pre>
class BodyPost
{
//
public static function PartPost($name, $val)
{
$body = 'Content-Disposition: form-data; name="' . $name . '"';
// oFile
if($val instanceof oFile)
{
//
$file = $val->Name();
// MIME
$mime = $val->Mime();
//
$cont = $val->Content();
$body .= '; filename="' . $file . '"' . "\r\n";
$body .= 'Content-Type: ' . $mime ."\r\n\r\n";
$body .= $cont."\r\n";
} else $body .= "\r\n\r\n".urlencode($val)."\r\n";
return $body;
}
// POST
public static function Get(array $post, $delimiter='-------------0123456789')
{
if(is_array($post) && !empty($post))
{
$bool = false;
//
foreach($post as $val) if($val instanceof oFile) {$bool = true; break; };
if($bool)
{
$ret = '';
// , POST
foreach($post as $name=>$val)
$ret .= '--' . $delimiter. "\r\n". self::PartPost($name, $val);
$ret .= "--" . $delimiter . "--\r\n";
} else $ret = http_build_query($post);
} else throw new \Exception('Error input param!');
return $ret;
}
};
</pre>
Cette classe se compose de plusieurs méthodes. La méthode PartPost forme les parties distinctes de la demande composite et la méthode Get combine ces parties et forme le corps de la demande POST au format - multipart / form-data.
Nous avons maintenant une classe générique pour envoyer le corps d'une requête POST. Il reste à écrire un programme qui utilise cette classe pour envoyer des fichiers à un serveur Web distant. Utilisons la bibliothèque CURL:
// -
include "ofile.class.php";
// POST
include "bodypost.class.php";
// POST
$delimiter = '-------------'.uniqid();
// oFile
$file = new oFile('sample.txt', 'text/plain', 'Content file');
// POST
$post = BodyPost::Get(array('field'=>'text', 'file'=>$file), $delimiter);
// CURL
$ch = curl_init();
//
curl_setopt($ch, CURLOPT_URL, 'http://server/upload/');
// , POST
curl_setopt($ch, CURLOPT_POST, 1);
// POST
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
/* :
Content-Type - ,
boundary -
Content-Length - */
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: multipart/form-data; boundary=' . $delimiter,
'Content-Length: ' . strlen($post)));
// POST Web
curl_exec($ch);
Si CURL ne convient pas, cette bibliothèque peut être utilisée pour l'envoi via des sockets. Eh bien, en fait des liens vers des sources:
- site de documentation php.net
- Article CURL: requĂŞte POST, contenu composite
- wikipedia: multipart / form-data
- RFC7578
Dans le prochain article, je vais vous fournir des informations sur la façon de télécharger des fichiers volumineux à partir de serveurs Web distants dans plusieurs flux à la vitesse spécifiée. À tous ceux qui ont lu jusqu'au bout, merci de votre attention!