Cómo hacer solicitudes HTTP en PHP y no esperar la respuesta

13 minutos de lectura

Avatar de usuario de Brent
Brent

¿Hay alguna forma en PHP de hacer llamadas HTTP y no esperar una respuesta? No me importa la respuesta, solo quiero hacer algo como file_get_contents(), pero no espere a que finalice la solicitud antes de ejecutar el resto de mi código. Esto sería muy útil para desencadenar “eventos” de algún tipo en mi aplicación o desencadenar procesos largos.

¿Algunas ideas?

  • una función: ‘curl_multi’, búsquela en los documentos php. Debería resolver tus problemas

    –James mayordomo

    17 de marzo de 2011 a las 0:09

  • El título de este post es engañoso. vine a buscar realmente llamadas asincrónicas similares a las solicitudes en Node.js o una solicitud AJAX. La respuesta aceptada no es asíncrona (bloquea y no proporciona una devolución de llamada), solo una solicitud sincrónica más rápida. Considere cambiar la pregunta o la respuesta aceptada.

    – Johntron

    29 de julio de 2013 a las 17:44

  • Jugar con el manejo de conexiones a través de encabezados y búfer no es infalible. Acabo de publicar una nueva respuesta independiente del sistema operativo, el navegador o la versión de PHP

    – Rafa Sashi

    21 de febrero de 2015 a las 13:39

  • Asincrónico no significa que no le importe la respuesta. Simplemente significa que la llamada no bloquea la ejecución del hilo principal. Asíncrono todavía requiere una respuesta, pero la respuesta se puede procesar en otro subproceso de ejecución o más tarde en un bucle de eventos. Esta pregunta solicita una solicitud de disparar y olvidar que puede ser síncrona o asíncrona según la semántica de entrega del mensaje, ya sea que le importe el orden del mensaje o la confirmación de la entrega.

    – CMC Dragonkai

    5 de marzo de 2015 a las 4:27

  • Creo que deberías hacer esta solicitud HTTP de disparo en modo sin bloqueo (w/c es lo que realmente quieres). Porque cuando llamas a un recurso, básicamente quieres saber si llegaste al servidor o no (o por cualquier motivo, simplemente necesita la respuesta). La mejor respuesta realmente es fsockopen y configurar la lectura o escritura de secuencias en modo sin bloqueo. Es como llamar y olvidar.

    – KiX Ortillan

    27 de noviembre de 2015 a las 6:48

La respuesta que había aceptado previamente no funcionó. Todavía esperaba respuestas. Sin embargo, esto funciona, tomado de ¿Cómo hago una solicitud GET asíncrona en PHP?

function post_without_wait($url, $params)
{
    foreach ($params as $key => &$val) {
      if (is_array($val)) $val = implode(',', $val);
        $post_params[] = $key.'='.urlencode($val);
    }
    $post_string = implode('&', $post_params);

    $parts=parse_url($url);

    $fp = fsockopen($parts['host'],
        isset($parts['port'])?$parts['port']:80,
        $errno, $errstr, 30);

    $out = "POST ".$parts['path']." HTTP/1.1\r\n";
    $out.= "Host: ".$parts['host']."\r\n";
    $out.= "Content-Type: application/x-www-form-urlencoded\r\n";
    $out.= "Content-Length: ".strlen($post_string)."\r\n";
    $out.= "Connection: Close\r\n\r\n";
    if (isset($post_string)) $out.= $post_string;

    fwrite($fp, $out);
    fclose($fp);
}

  • ¡Esto NO es asíncrono! En particular, si el servidor del otro lado está inactivo, este fragmento de código se bloqueará durante 30 segundos (el quinto parámetro en fsockopen). Además, fwrite tomará su tiempo para ejecutarse (que puede limitar con stream_set_timeout ($ fp, $ my_timeout). Lo mejor que puede hacer es establecer un tiempo de espera bajo en fsockopen a 0.1 (100 ms) y $ my_timeout a 100 ms Sin embargo, corre el riesgo de que se agote el tiempo de espera de la solicitud.

    – Chris Cinelli

    24/10/2012 a las 23:22

  • Os aseguro que es asíncrono, y no tarda ni 30 segundos. Eso es un tiempo de espera máximo. Es factible que su configuración sea diferente causando ese efecto, pero esto funcionó muy bien para mí.

    – Brent

    28 de noviembre de 2012 a las 21:37

  • @UltimateBrent No hay nada en el código que sugiera que es asíncrono. No espera una respuesta, pero eso no es asíncrono. Si el servidor remoto abre la conexión y luego se cuelga, este código esperará 30 segundos hasta que alcance ese tiempo de espera.

    – chmac

    6 de marzo de 2013 a las 10:04

  • la razón por la que parece funcionar “asincrónicamente” porque no lee desde el socket antes de cerrarlo, por lo que no se bloqueó incluso si el servidor no emitió una respuesta a tiempo. Sin embargo, esto no es asíncrono en absoluto. Si el búfer de escritura está lleno (muy poco probable), su secuencia de comandos definitivamente se colgará allí. Debería considerar cambiar su título a algo como “solicitar una página web sin esperar respuesta”.

    – cómoangghk

    26 de marzo de 2013 a las 4:42

  • Esto no es asíncrono ni usa curl, cómo te atreves a llamarlo curl_post_async y obtener incluso upvotes…

    – Daniel W.

    31 de octubre de 2013 a las 11:19


Si controla el objetivo al que desea llamar de forma asincrónica (por ejemplo, su propio “longtask.php”), puede cerrar la conexión desde ese extremo y ambos scripts se ejecutarán en paralelo. Funciona así:

  1. quick.php abre longtask.php a través de cURL (sin magia aquí)
  2. longtask.php cierra la conexión y continúa (¡magia!)
  3. cURL vuelve a quick.php cuando se cierra la conexión
  4. Ambas tareas continúan en paralelo

He probado esto, y funciona bien. Pero quick.php no sabrá nada sobre el rendimiento de longtask.php, a menos que cree algún medio de comunicación entre los procesos.

Pruebe este código en longtask.php, antes de hacer cualquier otra cosa. Cerrará la conexión, pero seguirá ejecutándose (y suprimirá cualquier salida):

while(ob_get_level()) ob_end_clean();
header('Connection: close');
ignore_user_abort();
ob_start();
echo('Connection Closed');
$size = ob_get_length();
header("Content-Length: $size");
ob_end_flush();
flush();

El código se copia de la Notas aportadas por el usuario del manual de PHP y algo mejorado.

  • Esto funcionaría. Pero si está utilizando un marco MVC, puede ser difícil de implementar debido a la forma en que este marco intercepta y reescribe las llamadas. Por ejemplo no funciona en un Controller en CakePHP

    – Chris Cinelli

    24/10/2012 a las 23:32


  • Una duda sobre este codigo, el proceso que debes hacer en longtask debe ir despues de estas lineas? Gracias.

    – morgar

    26 sep 2016 a las 17:11

  • No funciona perfectamente. Intenta agregar while(true); después de su código. La página se colgará, esto significa que aún se está ejecutando en primer plano.

    – Sos.

    10 de mayo de 2020 a las 3:25


  • ¿Cómo “lo abro a través de cURL”? ¿Cómo “creo algún medio de comunicación entre los procesos”?

    – Chico rojo11

    22 de enero de 2021 a las 1:04

Puede hacer trucos usando exec() para invocar algo que puede hacer solicitudes HTTP, como wgetpero debe dirigir toda la salida del programa a algún lugar, como un archivo o /dev/null, de lo contrario, el proceso de PHP esperará esa salida.

Si desea separar el proceso del hilo de apache por completo, intente algo como (no estoy seguro de esto, pero espero que entienda la idea):

exec('bash -c "wget -O (url goes here) > /dev/null 2>&1 &"');

No es un buen negocio, y probablemente querrá algo como un trabajo cron que invoque un script de latido que sondee una cola de eventos de base de datos real para hacer eventos asincrónicos reales.

  • Del mismo modo, también hice lo siguiente: exec(“curl $url > /dev/null &”);

    –Matt Huggins

    21 de septiembre de 2009 a las 18:50

  • Pregunta: ¿hay algún beneficio en llamar ‘bash -c “wget”‘ en lugar de solo ‘wget’?

    –Matt Huggins

    9 de noviembre de 2009 a las 21:18

  • En mis pruebas, usando exec("curl $url > /dev/null 2>&1 &"); es una de las soluciones más rápidas aquí. Es inmensamente más rápido (1,9 s para 100 iteraciones) que el post_without_wait() función (14.8s) en la respuesta “aceptada” anterior. Y es de una sola línea…

    – rinogo

    28/01/2016 a las 20:29

  • Use la ruta completa (por ejemplo, /usr/bin/curl) para hacerlo aún más rápido

    – Putnik

    25/10/2017 a las 21:42

  • ¿esto espera hasta que el guión esté terminado?

    – cikatomo

    12 abr 2020 a las 15:35

Puedes usar esta biblioteca: https://github.com/stil/curl-fácil

Es bastante sencillo entonces:

<?php
$request = new cURL\Request('http://yahoo.com/');
$request->getOptions()->set(CURLOPT_RETURNTRANSFER, true);

// Specify function to be called when your request is complete
$request->addListener('complete', function (cURL\Event $event) {
    $response = $event->response;
    $httpCode = $response->getInfo(CURLINFO_HTTP_CODE);
    $html = $response->getContent();
    echo "\nDone.\n";
});

// Loop below will run as long as request is processed
$timeStart = microtime(true);
while ($request->socketPerform()) {
    printf("Running time: %dms    \r", (microtime(true) - $timeStart)*1000);
    // Here you can do anything else, while your request is in progress
}

A continuación puede ver la salida de la consola del ejemplo anterior. Mostrará un reloj en vivo simple que indica cuánto tiempo se está ejecutando la solicitud:


animación

A partir de 2018, Engullir se ha convertido en la biblioteca estándar de facto para solicitudes HTTP, utilizada en varios marcos modernos. Está escrito en PHP puro y no requiere instalar ninguna extensión personalizada.

Puede hacer llamadas HTTP asincrónicas muy bien, e incluso juntarlos como cuando necesita realizar 100 llamadas HTTP, pero no desea ejecutar más de 5 a la vez.

Ejemplo de solicitud simultánea

use GuzzleHttp\Client;
use GuzzleHttp\Promise;

$client = new Client(['base_uri' => 'http://httpbin.org/']);

// Initiate each request but do not block
$promises = [
    'image' => $client->getAsync('/image'),
    'png'   => $client->getAsync('/image/png'),
    'jpeg'  => $client->getAsync('/image/jpeg'),
    'webp'  => $client->getAsync('/image/webp')
];

// Wait on all of the requests to complete. Throws a ConnectException
// if any of the requests fail
$results = Promise\unwrap($promises);

// Wait for the requests to complete, even if some of them fail
$results = Promise\settle($promises)->wait();

// You can access each result using the key provided to the unwrap
// function.
echo $results['image']['value']->getHeader('Content-Length')[0]
echo $results['png']['value']->getHeader('Content-Length')[0]

Ver http://docs.guzzlephp.org/en/stable/quickstart.html#concurrent-requests

  • Sin embargo, esta respuesta no es asíncrona. aparentemente guzzle no hace eso

    – daslicious

    21 de noviembre de 2018 a las 1:16


  • Guzzle requiere que instales curl. De lo contrario, no es paralelo y no le da ninguna advertencia de que no es paralelo.

    – Velizar Hristov

    23 de noviembre de 2018 a las 16:54

  • Gracias por el enlace @daslicious: sí, parece que no es completamente asíncrono (como cuando desea enviar una solicitud pero no le importa el resultado), pero algunas publicaciones en ese hilo un usuario ha ofrecido una solución alternativa establecer un valor de tiempo de espera de solicitud muy bajo que aún permite el tiempo de conexión, pero no espera el resultado.

    – Simón Este

    24 de noviembre de 2018 a las 6:27

  • composer require guzzle/guzzle ¡me da agrega 537 archivos y 2.5 millones de bytes de código nuevo a mi proyecto! ¡Para un cliente HTTP! No, gracias.

    – EricP

    13 de junio de 2021 a las 20:56

  • Necesitamos más personas como @EricP en nuestros proyectos.

    – mate

    17/03/2022 a las 13:35

avatar de usuario de philfreo
filfreo

/**
 * Asynchronously execute/include a PHP file. Does not record the output of the file anywhere. 
 *
 * @param string $filename              file to execute, relative to calling script
 * @param string $options               (optional) arguments to pass to file via the command line
 */ 
function asyncInclude($filename, $options="") {
    exec("/path/to/php -f {$filename} {$options} >> /dev/null &");
}

  • Sin embargo, esta respuesta no es asíncrona. aparentemente guzzle no hace eso

    – daslicious

    21 de noviembre de 2018 a las 1:16


  • Guzzle requiere que instales curl. De lo contrario, no es paralelo y no le da ninguna advertencia de que no es paralelo.

    – Velizar Hristov

    23 de noviembre de 2018 a las 16:54

  • Gracias por el enlace @daslicious: sí, parece que no es completamente asíncrono (como cuando desea enviar una solicitud pero no le importa el resultado), pero algunas publicaciones en ese hilo un usuario ha ofrecido una solución alternativa establecer un valor de tiempo de espera de solicitud muy bajo que aún permite el tiempo de conexión, pero no espera el resultado.

    – Simón Este

    24 de noviembre de 2018 a las 6:27

  • composer require guzzle/guzzle ¡me da agrega 537 archivos y 2.5 millones de bytes de código nuevo a mi proyecto! ¡Para un cliente HTTP! No, gracias.

    – EricP

    13 de junio de 2021 a las 20:56

  • Necesitamos más personas como @EricP en nuestros proyectos.

    – mate

    17/03/2022 a las 13:35

Avatar de usuario de la comunidad
Comunidad

  1. Falsificar una solicitud de aborto usando CURL estableciendo un nivel bajo CURLOPT_TIMEOUT_MS

  2. colocar ignore_user_abort(true) para seguir procesando después de cerrar la conexión.

Con este método, no es necesario implementar el manejo de la conexión a través de encabezados y búfer demasiado dependientes del sistema operativo, el navegador y la versión de PHP.

proceso maestro

function async_curl($background_process=""){

    //-------------get curl contents----------------

    $ch = curl_init($background_process);
    curl_setopt_array($ch, array(
        CURLOPT_HEADER => 0,
        CURLOPT_RETURNTRANSFER =>true,
        CURLOPT_NOSIGNAL => 1, //to timeout immediately if the value is < 1000 ms
        CURLOPT_TIMEOUT_MS => 50, //The maximum number of mseconds to allow cURL functions to execute
        CURLOPT_VERBOSE => 1,
        CURLOPT_HEADER => 1
    ));
    $out = curl_exec($ch);

    //-------------parse curl contents----------------

    //$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
    //$header = substr($out, 0, $header_size);
    //$body = substr($out, $header_size);

    curl_close($ch);

    return true;
}

async_curl('http://example.com/background_process_1.php');

proceso de fondo

ignore_user_abort(true);

//do something...

NÓTESE BIEN

Si desea que cURL se agote en menos de un segundo, puede usar CURLOPT_TIMEOUT_MS, aunque hay un error/”característica” en “sistemas similares a Unix” que hace que libcurl se agote inmediatamente si el valor es

[…]

La solución es deshabilitar las señales usando CURLOPT_NOSIGNAL

Recursos

  • ¿Cómo maneja el tiempo de espera de la conexión (resolver, dns)? Cuando configuro timeout_ms en 1, siempre termino con “resolver el tiempo de espera después de 4 ms” o algo así

    – Martín Wickman

    3 abr 2017 a las 13:53

  • No lo sé, pero 4 ms ya me parece bastante rápido… No creo que puedas resolverlo más rápido cambiando cualquier configuración de curl. Intente optimizar la solicitud específica tal vez…

    – Rafa Sashi

    3 abr 2017 a las 14:36

  • Ok, pero timeout_ms=1 establece el tiempo de espera para toda la solicitud. Entonces, si su resolución toma más de 1 ms, curl expirará y detendrá la solicitud. No veo cómo esto puede funcionar en absoluto (suponiendo que la resolución tome> 1 ms).

    – Martín Wickman

    4 de abril de 2017 a las 8:03

  • Si bien no tiene mucho sentido, funciona perfectamente y es una solución bastante buena para hacer PHP de forma asincrónica.

    – Jared Scarito

    16 de junio de 2021 a las 21:12


¿Ha sido útil esta solución?