Solicitação POST, conteúdo composto (multipart / form-data)

POST multipart / form-data


POSTANDO Dados Compostos



Na vida de qualquer programador, surgem problemas que prendem uma pessoa. Eu não gosto da solução padrão e é isso! E às vezes acontece que as soluções padrão não funcionam por algum motivo. Algumas pessoas ignoram essas tarefas, enquanto outras gostam de resolvê-las. Você pode até dizer que eles próprios os encontram. Uma dessas tarefas é enviar um arquivo ou vários arquivos usando o método POST.



Alguns provavelmente dirão que esta tarefa não é uma tarefa de todo. Afinal, existe uma biblioteca CURL maravilhosa que é bastante simples e resolve esse problema facilmente! Mas não tenha pressa. Sim, o CURL é uma biblioteca poderosa, sim, carrega arquivos, mas ... Como você sabe, tem um pequeno recurso - o arquivo deve ser colocado no seu disco rígido!



Agora vamos imaginar a seguinte situação, você está gerando um arquivo dinamicamente, ou ele já está na memória e você precisa enviá-lo usando o método POST para um servidor Web remoto. O que acontece depois? Você precisa salvá-lo antes de enviá-lo? Isso é exatamente o que 90% dos programadores fariam. Por que procurar problemas desnecessários se a solução está na superfície? Mas não estamos com vocês desses 90%! Estamos melhores, podemos resolver qualquer problema. Por que precisamos de uma ação extra? Primeiro, ele usa o sistema de arquivos não rápido do disco rígido. Em segundo lugar, podemos não ter acesso ao sistema de arquivos ou muito pouco espaço alocado nele.



Como podemos então resolver esse problema? Para fazer isso, você precisa observar como os dados são realmente transmitidos pelo método POST. A única solução é transferir o arquivo como uma solicitação composta usandomultipart / form-data . Essa técnica está bem documentada na RFC7578 . Vamos dar uma olhada em como será o corpo de uma solicitação POST multipart / form-data:

POST /form.html HTTP / 1.1
Host: server.com
Referer: http://server.com/form.html
Agente do usuário: Mozilla
Tipo de conteúdo: multipart / form-data; limite = ------------- 573cf973d5228
Comprimento do conteúdo: 288
Conexão: keep-alive
Keep-Alive: 300
(linha vazia)
(preâmbulo ausente)
--------------- 573cf973d5228
Content-Disposition: form-data; nome = "campo"

texto
--------------- 573cf973d5228
Content-Disposition: form-data; nome = "arquivo"; filename = "amostra.txt"
Tipo de conteúdo: texto / simples

Arquivo de conteúdo
--------------- 573cf973d5228--






Nosso corpo é composto por duas partes, na primeira parte passamos o valor do campo nome do formulário = "campo" igual a: texto . Na segunda parte, passamos o campo nome = "arquivo" com o conteúdo do arquivo nome do arquivo = "amostra.txt": Arquivo de conteúdo . No cabeçalho, especificamos o formato do conteúdo da solicitação POST - Content-Type: multipart / form-data , a string separadora das partes: boundary = ------------- 573cf973d5228 e o comprimento da mensagem - Content-Length: 288 .



Resta, de fato, escrever um programa que implemente esse método. Como somos pessoas inteligentes e não escrevemos a mesma coisa cem vezes em projetos diferentes, vamos organizar tudo na forma de uma classe que implemente este método. Além disso, vamos expandi-lo para diferentes opções de envio de arquivos e elementos de formulário simples. E para distinguir a presença de um arquivo entre a matriz de dados POST, vamos criar um arquivo separado - um contêiner com o conteúdo do arquivo e seus dados (nome e extensão). Assim, ficará assim:



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


Agora, a própria classe para formar o corpo multipart / form-data para a solicitação 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 classe consiste em vários métodos. O método PartPost forma as partes separadas da solicitação composta e o método Get combina essas partes e forma o corpo da solicitação POST no formato - multipart / form-data.



Agora temos uma classe genérica para enviar o corpo de uma solicitação POST. Resta escrever um programa que use essa classe para enviar arquivos a um servidor Web remoto. Vamos usar a 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);


Se o CURL não for adequado, essa biblioteca pode ser usada para enviar por meio de sockets. Bem, na verdade links para fontes:





No próximo artigo, fornecerei informações sobre como baixar arquivos grandes de servidores da Web remotos em vários fluxos na velocidade especificada. A todos que leram até o fim, obrigado pela atenção!



All Articles