tag soporta segundo script por optimizar lentas cuantas consultas attribute php jquery basex

php - soporta - title html css



Manera eficiente y fácil de presentar resultados de carga lenta (6)

He leído muchas preguntas similares relacionadas con la cancelación de una solicitud POST con jQuery, pero ninguna parece estar cerca de la mía.

Tengo tu forma diaria que tiene una página PHP como acción:

<form action="results.php"> <input name="my-input" type="text"> <input type="submit" value="submit"> </form>

El procesamiento de results.php en el lado del servidor, basado en la información de la publicación proporcionada en el formulario, lleva mucho tiempo (30 segundos o incluso más y esperamos un aumento porque nuestro espacio de búsqueda también aumentará en las próximas semanas). Estamos accediendo a un servidor Basex (versión 7.9, no actualizable) que contiene todos los datos. Un código XPath generado por el usuario se envía en un formulario, y la url de acción luego envía el código XPath al servidor Basex que devuelve los resultados. Desde una perspectiva de usabilidad, ya muestro una pantalla de "carga" para que los usuarios al menos sepan que se están generando los resultados:

$("form").submit(function() { $("#overlay").show(); }); <div id="overlay"><p>Results are being generated</p></div>

Sin embargo, también me gustaría darles a los usuarios la opción de presionar un botón para cancelar la solicitud y cancelarla cuando un usuario cierre la página. Tenga en cuenta que en el caso anterior (al hacer clic con el botón) esto también significa que el usuario debe permanecer en la misma página, puede editar su entrada y volver a enviar su solicitud de inmediato. Es primordial que cuando cancelen la solicitud, también puedan reenviarla de inmediato: el servidor realmente debería abortar, y no finalizar la consulta antes de poder procesar una nueva consulta.

Me imaginé algo como esto:

$("form").submit(function() { $("#overlay").show(); }); $("#overlay button").click(abortRequest); $(window).unload(abortRequest); function abortRequest() { // abort correct request } <div id="overlay"> <p>Results are being generated</p> <button>Cancel</button> </div>

Pero como puede ver, no estoy completamente seguro de cómo completar abortRequest para asegurarme de que la solicitud de publicación se aborta y finaliza , de modo que se pueda enviar una nueva consulta. ¡Por favor rellene los espacios en blanco! ¿O necesitaría .preventDefault() el envío del formulario y en su lugar hago una llamada ajax () desde jQuery?

Como dije, también quiero detener el proceso del lado del servidor, y por lo que leo, necesito exit() para esto. Pero, ¿cómo puedo exit otra función de PHP? Por ejemplo, digamos que en results.php tengo un script de procesamiento y necesito salir de ese script, ¿haría algo como esto?

<?php if (isset($_POST[''my-input''])) { $input = $_POST[''my-input'']; function processData() { // A lot of processing } processData() } if (isset($_POST[''terminate''])) { function terminateProcess() { // exit processData() } }

y luego hacer una nueva solicitud de ajax cuando necesito terminar el proceso?

$("#overlay button").click(abortRequest); $(window).unload(abortRequest); function abortRequest() { $.ajax({ url: ''results.php'', data: {terminate: true}, type: ''post'', success: function() {alert("terminated");}); }); }

Hice un poco más de investigación y encontré esta respuesta . Menciona connection_aborted () y también session_write_close () y no estoy del todo seguro de lo que es útil para mí. Utilizo las variables SESSION, pero no necesito escribir valores cuando se cancela el proceso (aunque me gustaría mantener las variables SESSION activas).

¿Sería este el camino? Y si es así, ¿cómo hago que una función de PHP termine la otra?

También he leído en Websockets y parece que podría funcionar, pero no me gusta la molestia de configurar un servidor Websocket, ya que esto requiere que me contacte con nuestro responsable de TI que requiere pruebas exhaustivas de nuevos paquetes. Prefiero mantenerlo en PHP y JS, sin bibliotecas de terceros que no sean jQuery.

Teniendo en cuenta que la mayoría de los comentarios y respuestas sugieren que lo que quiero no es posible, también me interesa escuchar alternativas. Lo primero que me viene a la mente son las llamadas Ajax paginadas (similar a muchas páginas web que ofrecen resultados de búsqueda, imágenes, lo que tienes en un desplazamiento infinito). A un usuario se le sirve una página con los X primeros resultados (por ejemplo, 20), y cuando hacen clic en el botón "mostrar los próximos 20 resultados", estos se muestran adjuntos. Este proceso puede continuar hasta que se muestren todos los resultados. Debido a que es útil para los usuarios obtener todos los resultados, también proporcionaré la opción "descargar todos los resultados". Esto también tomará mucho tiempo, pero al menos los usuarios deberían poder ver los primeros resultados en la página en sí. (Por lo tanto, el botón de descarga no debe interrumpir las cargas paginadas de Ajax). Es solo una idea, pero espero que les brinde algo de inspiración.


Con BaseX 8.4, se introdujo una nueva anotación RESTXQ %rest:single , que le permite cancelar una solicitud del lado del servidor en ejecución: http://docs.basex.org/wiki/RESTXQ#Query_Execution . Debería resolver al menos algunos de los desafíos que describiste.

La forma actual de devolver solo los fragmentos del resultado es pasar el índice al primer y último resultado de su resultado y hacer el filtrado en XQuery:

$results[position() = $start to $end]

Al devolver un resultado más del solicitado, el cliente sabrá que habrá más resultados. Esto puede ser útil, porque calcular el tamaño total del resultado es a menudo mucho más costoso que devolver solo los primeros resultados.


Debes resolver esto conceptualmente primero antes de escribir cualquier código. Aquí hay algunas cosas que vienen a la mente de improviso:

¿Qué significa liberar recursos en el servidor?

¿Qué constituye un aborto agraciado que liberará recursos?

¿Es suficiente matar el proceso de PHP a la espera de los resultados de la consulta? Si es así, la ruta sugerida por RandomSeed podría ser interesante. Solo tenga en cuenta que solo funcionará en un único servidor. Si tiene varios servidores con carga equilibrada, no tendrá una manera de detener un proceso en otro servidor (al menos no tan fácilmente).

¿O necesita cancelar la solicitud de base de datos desde la propia base de datos? En ese caso, la respuesta sugerida por Christian Grün es de mayor interés.

¿O es que no hay un cierre elegante y tienes que obligar a todo a morir? Si es así, esto parece terriblemente hacky.

No todos los clientes van a abortar explícitamente

Algunos clientes cerrarán el navegador, pero su última solicitud no se cumplirá; algunos clientes perderán la conexión a Internet y dejarán el servicio colgado, etc. No se garantiza que reciba una solicitud de "cancelación" cuando el cliente se desconecte o se haya ido.

Debe decidir si convive con un comportamiento potencialmente no deseado o si implementa un seguimiento de estado activo adicional, por ejemplo, el servidor de ping del cliente para el mantenimiento del estado.

Notas laterales

  • El tiempo de consulta de 30 segundos o más es potencialmente largo, ¿existe una mejor herramienta para el trabajo? ¿Así que no tendrás que resolver esto con un truco como este?

  • está buscando características de un sistema concurrente, pero no está usando un sistema concurrente; si desea la concurrencia, use una mejor herramienta / entorno para ello, por ejemplo, Erlang.


El usuario no tiene que experimentar esto sincrónicamente.

  1. El cliente publica una solicitud
  2. El servidor recibe la solicitud del cliente y le asigna un ID.
  3. El servidor "inicia" la búsqueda y responde con una página de datos cero y un ID de búsqueda
  4. El cliente recibe la página del "marcador de posición" y comienza a verificar si los resultados están listos en función de la ID (con algo como sondeo o websockets)
  5. Una vez que se ha completado la búsqueda, el servidor responde con los resultados la próxima vez que se sondea (o notifica al cliente directamente cuando usa websockets)

Esto está bien cuando el rendimiento no es el cuello de botella y la naturaleza del procesamiento hace que los tiempos de espera más largos sean aceptables. ¡Piense en los agregadores de búsqueda de vuelos que se ejecutan habitualmente durante 30-90 segundos, o en los generadores de informes que deben programarse y ejecutarse durante más tiempo!

Puede hacer que la experiencia sea menos frustrante si no bloquea las interacciones de los usuarios, manténgalos actualizados del progreso de la búsqueda y comience a mostrar los resultados en la medida de lo posible.


En mi entendimiento, los puntos clave son:

  • No puede cancelar una solicitud específica si se envía un formulario. Las razones son del lado del cliente por lo que no tiene nada, por lo que puede identificar los estados de una solicitud de formulario ( si se publica, si se está procesando, etc. ). Así que la única forma de cancelarlo es restablecer las variables $_POST y / o actualizar la página. Por lo tanto, la conexión se interrumpirá y la solicitud anterior no se completará.

  • En su solución alternativa cuando está enviando otra llamada Ajax con {terminate: true} el result.php puede detener el procesamiento con un simple die() . Pero como será una llamada async , no podrá asignarla con el form submit anterior. Así que esto no funcionará prácticamente.

  • Solución probable : enviar el formulario con Ajax. Con jQuery ajax tendrá un objeto xhr que puede abort() al descargar la ventana.

ACTUALIZACIÓN (sobre el comentario):

  • Una solicitud sincrónica es cuando su página se bloqueará ( todas las acciones del usuario ) hasta que el resultado esté listo. Al presionar un botón de envío en el formulario, haga una llamada sincrónica al servidor enviando el formulario, por definición [ https://www.w3.org/TR/html-markup/button.submit.html ].

  • Ahora, cuando el usuario ha presionado el botón enviar, la conexión del navegador al servidor es sincrónica, por lo que no se verá obstaculizada hasta que el resultado esté allí. Por lo tanto, cuando se realizan otras llamadas al servidor, durante el proceso de envío, ninguna referencia de esta operación está disponible para otros, ya que no está terminada. Es la razón por la que el envío de la llamada de terminación con Ajax no funcionará.

  • En tercer lugar: para su caso puede considerar el siguiente ejemplo de código:

HTML :

<form action="results.php"> <input name="my-input" type="text"> <input id="resultMaker" type="button" value="submit"> </form> <div id="overlay"> <p>Results are being generated</p> <button>Cancel</button> </div>

JQUERY :

<script type="text/javascript"> var jqXhr = ''''; $(''#resultMaker'').on(''click'', function(){ $("#overlay").show(); jqXhr = $.ajax({ url: ''results.php'', data: $(''form'').serialize(), type: ''post'', success: function() { $("#overlay").hide(); }); }); }); var abortRequest = function(){ if (jqXhr != '''') { jqXhr.abort(); } }; $("#overlay button").on(''click'', abortRequest); window.addEventListener(''unload'', abortRequest); </script>

Este es un código de ejemplo. Acabo de usar sus ejemplos de código y he cambiado algo aquí y allá.


Espero haber entendido esto correctamente.

En lugar de permitir que el navegador envíe el FORM "de forma nativa", no: escriba el código JS que hace esto en su lugar. En otras palabras (no probé esto, así que interpreta como pseudocódigo):

<form action="results.php" onsubmit="return false;"> <input name="my-input" type="text"> <input type="submit" value="submit"> </form>

Entonces, ahora, cuando se hace clic en el botón "enviar", no ocurrirá nada.

Obviamente, desea que su formulario sea POSTED, así que escriba JS para adjuntar un controlador de clic en ese botón de envío, recopile los valores de todos los campos de entrada en el formulario (en realidad, NO es tan aterrador como parece; consulte el enlace a continuación), y envíelo al servidor, mientras guarda la referencia a la solicitud (verifique el segundo enlace a continuación), de modo que pueda abortar (y quizás también indicar al servidor que salga) cuando se haga clic en el botón de cancelar (alternativamente, puede simplemente abandónelo, sin preocuparse por los resultados).

Enviar un formulario utilizando jQuery

Abortar peticiones Ajax usando jQuery

Alternativamente, para hacer que el marcado HTML sea "más claro" en relación con su funcionalidad, considere no usar la etiqueta FORM: de lo contrario, lo que sugerí hace que su uso sea confuso (por qué está ahí si no se usa; ¿entiendo?). Pero, no te distraigas con esta sugerencia hasta que hagas que funcione como quieres; es opcional y es un tema para otro día (incluso podría estar relacionado con la arquitectura cambiante de todo el sitio).

SIN EMBARGO, una cosa en que pensar: ¿qué hacer si el formulario ya llegó al servidor y el servidor ya comenzó a procesarlo y ya se han realizado algunos cambios "mundiales"? Tal vez su rutina de obtención de resultados no cambie los datos, entonces está bien. Sin embargo, este enfoque probablemente no se puede utilizar con POST de datos de cambio con la expectativa de que "mundo" no cambiará si se hace clic en el botón de cancelar.

Espero que eso ayude :)


Himel Nag Rana demostró cómo cancelar una solicitud de Ajax pendiente. Varios factores pueden interferir y demorar las solicitudes posteriores, como lo he comentado anteriormente en otra publicación .

TL; DR: 1. es muy incómodo intentar detectar que la solicitud se canceló desde la propia tarea de larga duración y 2. como solución alternativa, debe cerrar la sesión ( session_write_close() ) tan pronto como sea posible en su larga duración. Tarea en ejecución para no bloquear solicitudes posteriores.

connection_aborted() no se puede utilizar. Se supone que esta función se debe llamar periódicamente durante una tarea larga (normalmente, dentro de un bucle). Desafortunadamente, solo hay una única operación atómica significativa en su caso: la consulta al final de los datos.

Si aplicó los procedimientos recomendados por Himel Nag Rana y por mí mismo, ahora debería poder cancelar la solicitud de Ajax e inmediatamente permitir que continúen las nuevas solicitudes. La única preocupación que queda es que la solicitud previa (cancelada) puede continuar ejecutándose en segundo plano por un tiempo (sin bloquear al usuario, solo desperdiciando recursos en el servidor).

El problema podría reformularse como "cómo abortar un proceso específico desde el exterior".

Como Christian Bonato aconsejó con razón , aquí hay una posible implementación. Por el bien de la demostración, dependeré del componente Process de Symphony , pero puede idear una solución personalizada más sencilla si lo prefiere.

El enfoque básico es:

  1. Genere un nuevo proceso para ejecutar la consulta, guarde el PID en la sesión. Espere a que se complete, luego devuelva el resultado al cliente

  2. Si el cliente cancela, le indica al servidor que simplemente detenga el proceso.


<?php // query.php use Symfony/Component/Process/PhpProcess; session_start(); if(isset($_SESSION[''queryPID''])) { // A query is already running for this session // As this should never happen, you may want to raise an error instead // of just silently killing the previous query. posix_kill($_SESSION[''queryPID''], SIGKILL); unset($_SESSION[''queryPID'']); } $queryString = parseRequest($_POST); $process = new PhpProcess(sprintf( ''<?php $result = runQuery(%s); echo fetchResult($result);'', $queryString )); $process->start(); $_SESSION[''queryPID''] = $process->getPid(); session_write_close(); $process->wait(); $result = $process->getOutput(); echo formatResponse($result); ?>


<?php // abort.php session_start(); if(isset($_SESSION[''queryPID''])) { $pid = $_SESSION[''queryPID'']; posix_kill($pid, SIGKILL); unset($pid); echo "Query $pid has been aborted"; } else { // there is nothing to abort, send a HTTP error code header($_SERVER[''SERVER_PROTOCOL''] . '' 599 No pending query'', true, 599); } ?>


// javascript function abortRequest(pendingXHRRequest) { pendingXHRRequest.abort(); $.ajax({ url: ''abort.php'', success: function() { alert("terminated"); }); }); }

Generar un proceso y seguirlo es realmente complicado, por eso aconsejé utilizar los módulos existentes. Integrar solo un componente de Symfony debería ser relativamente fácil a través de Composer : primero instale Composer , luego el componente Process (el composer require symfony/process ).

Una implementación manual podría tener este aspecto (cuidado, esto no está probado, está incompleto y posiblemente sea inestable, pero confío en que obtendrás la idea):

<?php // query.php session_start(); $queryString = parseRequest($_POST); // $queryString should be escaped via escapeshellarg() $processHandler = popen("/path/to/php-cli/php asyncQuery.php $queryString", ''r''); // fetch the first line of output, PID expected $pid = fgets($processHandler); $_SESSION[''queryPID''] = $pid; session_write_close(); // fetch the rest of the output while($line = fgets($processHandler)) { echo $line; // or save this line for further processing, e.g. through json_encode() } fclose($processHandler); ?>


<?php // asyncQuery.php // echo the current PID echo getmypid() . PHP_EOL; // then execute the query and echo the result $result = runQuery($argv[1]); echo fetchResult($result); ?>