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:
- site de documentação php.net
- Artigo CURL: solicitação POST, conteúdo composto
- wikipedia: multipart / form-data
- RFC7578
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!