ti-enxame.com

PHP file_get_contents muito lento ao usar o URL completo

Eu estou trabalhando com um script (que eu não criei originalmente) que gera um arquivo pdf de uma página HTML. O problema é que agora está demorando muito tempo, como 1-2 minutos, para ser processado. Supostamente isso estava funcionando bem originalmente, mas desacelerou nas duas últimas semanas.

O script chama file_get_contents em um script php, que então gera o resultado em um arquivo HTML no servidor e executa o aplicativo gerador de pdf nesse arquivo.

Eu pareço ter reduzido o problema para a chamada file_get_contents em um URL completo, em vez de um caminho local.

Quando eu uso

$content = file_get_contents('test.txt');

processa quase instantaneamente. No entanto, se eu usar o URL completo

$content = file_get_contents('http://example.com/test.txt');

leva de 30 a 90 segundos para processar.

Não se limita ao nosso servidor, é lento ao acessar qualquer URL externa, como http://www.google.com . Eu acredito que o script chama o URL completo porque há variáveis ​​de string de consulta que são necessárias e que não funcionam se você chamar o arquivo localmente.

Eu também tentei fopen, readfile e curl, e eles eram todos igualmente lentos. Alguma idéia de onde procurar para consertar isso?

57
ecurbh

Nota: Isto foi corrigido em PHP 5.6.14. Um cabeçalho Connection: close agora será enviado automaticamente para solicitações HTTP/1.0. Veja commit 4b1dff6 .

Eu tive dificuldade em descobrir a causa da lentidão dos scripts file_get_contents.

Ao analisá-lo com o Wireshark, o problema (no meu caso e provavelmente o seu também) era que o servidor remoto NÃO FECHAR A CONEXÃO TCP ATÉ 15 SEGUNDOS (ou seja, "keep-alive").

De fato, file_get_contents não envia um cabeçalho HTTP de "conexão", portanto, o servidor web remoto considera por padrão que é uma conexão keep-alive e não fecha o fluxo TCP até 15 segundos (pode não ser um valor padrão - depende do servidor conf).

Um navegador normal consideraria que a página está totalmente carregada se o comprimento da carga útil HTTP atingir o comprimento especificado no cabeçalho HTTP Content-Length da resposta. File_get_contents não faz isso e isso é uma vergonha.

SOLUÇÃO

Então, se você quiser saber a solução, aqui está:

$context = stream_context_create(array('http' => array('header'=>'Connection: close\r\n')));
file_get_contents("http://www.something.com/somepage.html",false,$context);

A única coisa é apenas dizer ao servidor web remoto para fechar a conexão quando o download estiver completo, já que file_get_contents não é inteligente o suficiente para fazê-lo sozinho usando o cabeçalho HTTP Content-Length da resposta.

171
KrisWebDev

Eu usaria curl () para buscar conteúdo externo, pois isso é muito mais rápido que o método file_get_contents. Não tenho certeza se isso vai resolver o problema, mas vale a pena um tiro.

Observe também que a velocidade de seus servidores afetará o tempo necessário para recuperar o arquivo. 

Aqui está um exemplo de uso:

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'http://example.com/test.txt');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
curl_close($ch);
29
Brad F Jacobs

Às vezes, é porque o DNS está muito lento no seu servidor, tente isto:

substituir

echo file_get_contents('http://www.google.com');

como

$context=stream_context_create(array('http' => array('header'=>"Host: www.google.com\r\n")));
echo file_get_contents('http://74.125.71.103', false, $context);
5
diyism

Eu tive o mesmo problema, 

A única coisa que funcionou para mim é definir o tempo limite no array $options.

$options = array(
    'http' => array(
        'header'  => implode($headers, "\r\n"),
        'method'  => 'POST',
        'content' => '',
        'timeout' => .5
    ),
);
3
Walid Ammar

Você pode tentar buscar esse URL, no servidor, a partir da linha de comando? Curl ou wget vêm à mente. Se esses recuperar a URL em uma velocidade normal, então não é um problema de rede e provavelmente algo na configuração do Apache/php.

1
Marc B

Eu tenho um enorme dado passado por API, estou usando file_get_contents para ler os dados, mas demorou cerca de 60 segundos. No entanto, usando a solução do KrisWebDev, demorou cerca de 25 segundos.

$context = stream_context_create(array('https' => array('header'=>'Connection: close\r\n')));
file_get_contents($url,false,$context);
0
Elyor

Eu sei que é uma pergunta antiga, mas eu a encontrei hoje e as respostas não funcionaram para mim. Eu não vi ninguém dizendo que o máximo de conexões por IP pode ser definido como 1. Dessa forma, você está fazendo a solicitação da API e a API está fazendo outra solicitação porque você usa o URL completo. É por isso que o carregamento direto do disco funciona. Para mim, isso resolveu um problema:

if (strpos($file->url, env('APP_URL')) === 0) {
    $url = substr($file->url, strlen(env('APP_URL')));
} else {
    $url = $file->url;
}
return file_get_contents($url);
0
ElChupacabra

O que eu também consideraria com o Curl é que você pode "encadear" as solicitações. Isso me ajudou imensamente, já que não tenho acesso a uma versão do PHP que permita encadeamento no momento. 

Por exemplo, eu estava obtendo 7 imagens de um servidor remoto usando file_get_contents e levava de 2 a 5 segundos por solicitação. Somente esse processo adicionava 30 segundos ou algo ao processo, enquanto o usuário esperava que o PDF fosse gerado. 

Isso literalmente reduziu o tempo para cerca de 1 imagem. Outro exemplo, eu verifico 36 URLs no tempo que levei antes para fazer um. Eu acho que você entendeu. :-)

    $timeout = 30;
    $retTxfr = 1;
    $user = '';
    $pass = '';

    $master = curl_multi_init();
    $node_count = count($curlList);
    $keys = array("url");

    for ($i = 0; $i < $node_count; $i++) {
        foreach ($keys as $key) {
            if (empty($curlList[$i][$key])) continue;
            $ch[$i][$key] = curl_init($curlList[$i][$key]);
            curl_setopt($ch[$i][$key], CURLOPT_TIMEOUT, $timeout); // -- timeout after X seconds
            curl_setopt($ch[$i][$key], CURLOPT_RETURNTRANSFER, $retTxfr);
            curl_setopt($ch[$i][$key], CURLOPT_HTTPAUTH, CURLAUTH_ANY);
            curl_setopt($ch[$i][$key], CURLOPT_USERPWD, "{$user}:{$pass}");
            curl_setopt($ch[$i][$key], CURLOPT_RETURNTRANSFER, true);
            curl_multi_add_handle($master, $ch[$i][$key]);
        }
    }

    // -- get all requests at once, finish when done or timeout met --
    do {  curl_multi_exec($master, $running);  }
    while ($running > 0);

Em seguida, verifique os resultados:

            if ((int)curl_getinfo($ch[$i][$key], CURLINFO_HTTP_CODE) > 399 || empty($results[$i][$key])) {
                unset($results[$i][$key]);
            } else {
                $results[$i]["options"] = $curlList[$i]["options"];
            }
            curl_multi_remove_handle($master, $ch[$i][$key]);
            curl_close($ch[$i][$key]);

então feche o arquivo:

    curl_multi_close($master);
0
Mike Q
$context = stream_context_create(array('http' => array('header'=>'Connection: close\r\n')));
$string = file_get_contents("http://localhost/testcall/request.php",false,$context);

Tempo: 50976 ms (tempo total no total 5 tentativas)

$ch = curl_init();
$timeout = 5;
curl_setopt($ch, CURLOPT_URL, "http://localhost/testcall/request.php");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
echo $data = curl_exec($ch);
curl_close($ch);

Tempo: 46679 ms (tempo total no total 5 tentativas)

Nota: request.php é usado para buscar alguns dados do banco de dados mysql.

0
Amito