Dev (Back & Front)ARTIGO

Non-blocking asynchronous requests usando curl_multi e php

Outro dia eu precisei implementar um
script que, ao receber alguns dados enviados por POST pelos usuários do
site, faz uma requisição http a uma api externa usando parte desses
dados postados. Como as informações que essa api retorna não precisam
ser exibidas para o usuário, e como essa api geralmente leva cerca de 2
segundos pra responder, para não deixar o usuário “pendurado”
esperando, resolvi que faria uma requisição assíncrona não-bloqueante
para um outro script que, por sua vez, acessaria a api e iria
tratar/salvar os dados que eu necessitava.

Como o phpPHP44 conteúdosPadronizando seu código com PHP CS FixerDev (Back & Front) · mai 2019Docker de imagem. Para rodar projetos PHP em menos de 30 segundosDev (Back & Front) · jan 2026PHP 7.3: conheça as novidades desta versãoDev (Back & Front) · abr 2019Ver tudo em Dev (Back & Front) não tem suporte a threads, a
minha solução foi implementada com a biblioteca curl, mais
especificamente com a função curl_multi_*(), que permite fazer
requisições paralelas e assíncronas. Porém, os exemplos que encontrei
tanto na documentação no php.net quanto em classes disponibilizadas por
terceiros não funcionavam exatamente do jeito que eu queria, e acabei
quebrando a cabeça por algumas horas para encontrar a solução, que
gostaria de compartilhar aqui.

A abordagem quase unânime para uso do
multi_curl com as quais me deparei propunham o uso dela para fazer as
requisições paralelas não bloqueantes, executar em seguida algum
processamento não relacionado às requisições (por exemplo, fazer um
select no banco de dadosBanco de dados134 conteúdosSQL ou NoSQL: eis a questão!!Data · mar 2020Banco de dados: como organizar e dar segurança para milhões de dados de loteriasData · mai 20215 serviços gratuitos na cloud para bancos de dados PostgresData · fev 2025Ver tudo em Data ), e depois ficar em busy-waiting até que todas
as requisições recebam uma resposta, para então finalizar o script. Um
exemplo de como fazer isso seria:

// Inicializa um multi-curl handle<br />$mch = curl_multi_init();<br />// Inicializa e seta as opções para cada requisição<br />$ch1 = curl_init('http://www.yahoo.com');<br />curl_setopt($ch1, CURLOPT_RETURNTRANSFER);<br />$ch2 = curl_init('http://www.google.com');<br />curl_setopt($ch2, CURLOPT_RETURNTRANSFER);<br />// Adiciona a requisição $ch1 ao multi-curl handle $mch.<br />curl_multi_add_handle($mch, $ch1);<br /><br />// Executa requisição multi-curl e retorna imediatamente.<br />curl_multi_exec($mch, $active);<br /><br />// Repete o procedimento para a requisição $ch2<br />curl_multi_add_handle($mch, $ch2);<br />curl_multi_exec($mch, $active);<br /><br />// Executa outros processamentos<br /><br />// Fica em busy-waiting até que todas as requisições retornem<br />do{<br />curl_multi_exec($mch, $active);<br />}while($active > 0);<br /><br />// Acessa as respostas das requisições<br />$resp1 = curl_multi_getcontent($ch1);<br />$resp2 = curl_multi_getcontent($ch2);

No meu caso, como eu não precisava das
respostas, inicialmente tentei usar este mesmo código, removendo o “do
{ } while ( )” e as chamadas à curl_multi_getcontent(), isso porque
tanto o manual do php.net quanto os textos que li dão a entender que uma
única chamada a curl_multi_exec() seria suficiente pra iniciar as
requisições assíncronas e retornar imediatamente. Não foi bem o que
aconteceu com o meu script, que só fazia a requisição de fato quando eu
deixava o curl_multi_exec() em loop, o que não adiantava pra mim, pois
o loop só finalizava depois que a mesma retornava uma resposta.

Eis que, depois de algumas horas
pesquisando, encontrei o seguinte comentário: “curl_multi_perform is
asynchronous. It will only execute as little as possible and then
return back control to your program. It is designed to never block. If
it returns CURLM_CALL_MULTI_PERFORM you better call it again soon, as
that is a signal that it still has local data to send or remote data to
receive.”

Assim, pra resolver o meu problema
bastou substituir a chamada única à curl_multi_exec e o loop que ficava
em busy-waiting enquanto a conexão estivesse ativa por um loop que faz
chamadas a curl_multi_exec somente enquanto a constante
CURLM_CALL_MULTI_PERFORM estivesse retornando TRUE. No caso, o código
pra fazer isso seria:

do {<br />$res = curl_multi_exec($mch, $active);<br />} while ($res == CURLM_CALL_MULTI_PERFORM);

Deste modo, a requisição é feita (nos
meus testes, três iterações deste loop foram suficientes para concluir
o envio) e rapidamente retorna o controle para o script, que pode ser
finalizado sem esperar por uma resposta, deixando todo mundo (o
usuário, o programador e o servidor) feliz 🙂

P.S.: enquanto pesquisava uma solução,
me deparei com algumas classes e funções interessantes para usar o
curl_multi. Uma delas está neste post,
que contém a implementação eficiente de uma função para quem precisar
fazer grande número de requisições em paralelo e processar os
resultados à medida que forem retornando. Vai me ser útil num futuro
próximo.

mora em Vitória-ES, é empreendedor e desenvolvedor na internet há 10 anos, co-fundador do Flogao.com.br e (quase) bacharel em Ciência da Computação pela UFES. www.sanainside.com

Ver perfil