Solicitud POST, contenido compuesto (multiparte / form-data)

POST de varias partes / datos de formulario


Publicación de datos compuestos



En la vida de cualquier programador, surgen problemas que atrapan a una persona. No me gusta la solución estándar y eso es todo. Y a veces sucede que las soluciones estándar no funcionan por alguna razón. Algunas personas pasan por alto estas tareas, mientras que a otras les gusta resolverlas. Incluso puedes decir que los encuentran ellos mismos. Una de esas tareas es enviar un archivo o varios archivos mediante el método POST.



Algunos probablemente dirán que esta tarea no es una tarea en absoluto. Después de todo, existe una maravillosa biblioteca CURL que es bastante simple y resuelve este problema fácilmente. Pero no tengas prisa. Sí, CURL es una biblioteca poderosa, sí, carga archivos, pero ... Como sabes, tiene una pequeña característica: ¡el archivo debe colocarse en tu disco duro!



Ahora imaginemos la siguiente situación, está generando dinámicamente un archivo o ya está en la memoria y necesita enviarlo usando el método POST a un servidor web remoto. ¿Qué pasa entonces? ¿Necesitas guardarlo antes de enviarlo? Esto es exactamente lo que haría el 90% de los programadores. ¿Por qué buscar problemas innecesarios si la solución está en la superficie? ¡Pero no estamos contigo de este 90%! Somos mejores, podemos solucionar cualquier problema. ¿Por qué necesitamos una acción adicional? Primero, utiliza el sistema de archivos no rápido del disco duro. En segundo lugar, es posible que no tengamos acceso al sistema de archivos o que haya asignado muy poco espacio allí.



¿Cómo podemos entonces resolver este problema? Para hacer esto, debe observar cómo se transmiten realmente los datos mediante el método POST. La única solución es transferir el archivo como una solicitud compuesta usandomultipart / form-data . Esta técnica está bien documentada en RFC7578 . Echemos un vistazo a cómo se verá el cuerpo de una solicitud POST de múltiples partes / datos de formulario:

POST /form.html HTTP / 1.1
Anfitrión: server.com
Referente: http://server.com/form.html
Usuario-Agente: Mozilla
Tipo de contenido: multiparte / form-data; límite = ------------- 573cf973d5228
Longitud del contenido: 288
Conexión: mantener vivo
Mantener vivo: 300
(línea vacía)
(falta preámbulo)
--------------- 573cf973d5228
Disposición de contenido: datos de formulario; nombre = "campo"

texto
--------------- 573cf973d5228
Disposición de contenido: datos de formulario; nombre = "archivo"; filename = "sample.txt"
Tipo de contenido: texto / sin formato

Archivo de contenido
--------------- 573cf973d5228--






Nuestro cuerpo consta de dos partes, en la primera parte pasamos el valor del formulario campo nombre = "campo" igual a: texto . En la segunda parte, pasamos el campo name = "file" con el contenido del file filename = "sample.txt": Content file . En el encabezado, especificamos el formato del contenido de la solicitud POST - Content-Type: multipart / form-data , la cadena de separación de partes: boundary = ------------- 573cf973d5228 y la longitud del mensaje - Content-Length: 288 .



De hecho, queda por escribir un programa que implemente este método. Como somos gente inteligente y no escribimos lo mismo cien veces en proyectos diferentes, organizaremos todo en forma de una clase que implemente este método. Además, expandámoslo para diferentes opciones para enviar archivos y elementos de formulario simples. Y para distinguir la presencia de un archivo entre la matriz de datos POST, creemos un archivo separado: un contenedor con el contenido del archivo y sus datos (nombre y extensión). Por lo tanto, se verá así:



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


Ahora, la propia clase para formar el cuerpo multipart / form-data para la solicitud 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>


Esta clase consta de varios métodos. El método PartPost forma las partes separadas de la solicitud compuesta, y el método Get combina estas partes y forma el cuerpo de la solicitud POST en el formato - multipart / form-data.



Ahora tenemos una clase genérica para enviar el cuerpo de una solicitud POST. Queda por escribir un programa que utilice esta clase para enviar archivos a un servidor web remoto. Usemos la biblioteca 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 no es adecuado, esta biblioteca se puede utilizar para enviar a través de sockets. Bueno, en realidad enlaces a fuentes:





En el siguiente artículo, le proporcionaré información sobre cómo descargar archivos grandes desde servidores web remotos en múltiples secuencias a la velocidad especificada. A todos los que leyeron hasta el final, ¡gracias por su atención!



All Articles