Подтвердить что ты не робот

Эффективный и удобный способ представления результатов с низкой загрузкой

Я прочитал много похожих вопросов об отмене запроса POST с помощью jQuery, но ни один из них не кажется мне близким.

У меня есть ваша повседневная форма, в которой есть PHP-страница как действие:

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

Обработка results.php на стороне сервера, основанная на информации о сообщении, приведенной в форме, занимает много времени (30 секунд или даже больше, и мы ожидаем увеличения, потому что наше поисковое пространство также увеличится в ближайшие недели). Мы обращаемся к серверу Basex (версия 7.9, не обновляемая), которая содержит все данные. Созданный пользователем код XPath представляется в форме, и URL-адрес действия отправляет код XPath на сервер Basex, который возвращает результаты. С точки зрения удобства использования я уже показываю экран загрузки, поэтому пользователи, по крайней мере, знают, что результаты генерируются:

$("form").submit(function() {
  $("#overlay").show();
});

<div id="overlay"><p>Results are being generated</p></div>

Однако я также хотел бы дать пользователям возможность нажать кнопку, чтобы отменить запрос и отменить запрос, когда пользователь закрывает страницу. Обратите внимание, что в первом случае (при нажатии кнопки) это также означает, что пользователь должен оставаться на одной странице, редактировать свои данные и сразу же повторно отправлять запрос. Крайне важно, чтобы при отмене запроса они также могли сразу же отправить его повторно: сервер должен действительно прервать и не завершить запрос перед тем, как обработать новый запрос.

Я понял что-то вроде этого:

$("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>

Но, как вы можете видеть, я не совсем уверен, как заполнить abortRequest, чтобы убедиться, что запрос на отправку отменен и завершен, чтобы можно было отправить новый запрос. Пожалуйста, заполните пробелы! Или мне нужно .preventDefault() передать форму и вместо этого выполнить вызов ajax() из jQuery?


Как я уже сказал, я также хочу остановить процесс на стороне сервера, и из того, что я прочитал, мне нужно exit() для этого. Но как я могу exit еще одна функция PHP? Например, скажем, что в results.php у меня есть обработка script, и мне нужно выйти из этого script, я бы сделал что-то вроде этого?

<?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()
    }
  }

а затем выполнить новый запрос ajax, когда мне нужно завершить процесс?

$("#overlay button").click(abortRequest);
$(window).unload(abortRequest);

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

Я сделал еще несколько исследований, и я нашел этот ответ. Он упоминает connection_aborted(), а также session_write_close(), и я не совсем уверен, что полезно для меня. Я использую переменные SESSION, но мне не нужно записывать значения, когда процесс отменяется (хотя я бы хотел, чтобы переменные SESSION активны).

Будет ли это так? И если да, то как сделать одну функцию PHP завершающей другую?


Я также читал в Websockets, и кажется, что это может сработать, но мне не нравится проблема настройки сервера Websocket, так как это потребует от меня контакта с нашим ИТ-парнем, который требует обширного тестирования новых пакетов. Я бы предпочел сохранить его в PHP и JS, без сторонних библиотек, кроме jQuery.

Учитывая большинство комментариев и ответов, я считаю, что то, чего я хочу, невозможно, мне также интересно услышать альтернативы. Первое, что приходит на ум, - это вызывные вызовы Ajax (похожие на многие веб-страницы, которые служат для поиска результатов, изображений, "что-вы" в бесконечном прокрутке). Пользователь получает страницу с первыми результатами X (например, 20), а при нажатии кнопки "показать следующие 20 результатов", которые показаны, добавляются. Этот процесс может продолжаться до тех пор, пока не будут показаны все результаты. Поскольку пользователям полезно получить все результаты, я также предоставил опцию "загрузить все результаты". Это также займет очень много времени, но по крайней мере пользователи должны иметь возможность пройти первые результаты на самой странице. (Таким образом, кнопка загрузки не должна нарушать загруженные пользователем загрузки Ajax.) Это просто идея, но я надеюсь, что это даст некоторым из вас вдохновение.

4b9b3361

Ответ 1

Himel Nag Rana продемонстрировал, как отменить ожидающий запрос Ajax. Несколько факторов могут помешать и отложить последующие запросы, как я уже говорил ранее в другом сообщении.

TL; DR: 1. очень неудобно пытаться обнаружить, что запрос был отменен из самой длительной задачи, а 2. в качестве обходного пути вы должны закрыть сеанс (session_write_close()) как можно раньше в вашей долговременной задаче, чтобы не блокировать последующие запросы.

connection_aborted() не может использоваться. Предполагается, что эта функция должна вызываться периодически во время длительной задачи (как правило, внутри цикла). К сожалению, в вашем случае есть только одна значительная, атомная операция: запрос к завершению работы.

Если вы применили процедуры, рекомендованные Himel Nag Rana и мной, теперь вы сможете отменить запрос Ajax и немедленно разрешить новые запросы. Единственное, что остается, это то, что предыдущий (отмененный) запрос может некоторое время работать в фоновом режиме (не блокируя пользователя, просто тратя ресурсы на сервер).

Проблема может быть перефразирована как "как прервать определенный процесс извне".

Как Кристиан Бонатон по праву посоветовал, вот возможная реализация. Для демонстрации я буду полагаться на Symphony Process component, но вы можете разработать более простое пользовательское решение, если хотите.

Основной подход:

  • Создайте новый процесс для запуска запроса, сохраните PID в сеансе. Подождите, пока он завершится, а затем верните результат клиенту.

  • Если клиент прерывается, он сигнализирует серверу просто убить процесс.


<?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"); });
      });
}

Появление процесса и отслеживание его действительно сложно, поэтому я посоветовал использовать существующие модули. Интеграция только одного компонента Symfony должна быть относительно легко через Composer: сначала установить Composer, затем компонент Process (composer require symfony/process).

Ручная реализация может выглядеть так (остерегайтесь, это непроверено, неполно и, возможно, нестабильно, но я надеюсь, вы получите эту идею):

<?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);

?>

Ответ 2

По моему мнению, ключевыми моментами являются:

  • Вы не можете отменить конкретный запрос, если форма отправлена. Причины на стороне клиента у вас нет, чтобы вы могли идентифицировать состояния запроса формы (если оно опубликовано, если оно обрабатывается и т.д.). Таким образом, единственный способ отменить это - reset переменные $_POST и/или обновить страницу. Таким образом, соединение будет нарушено, и предыдущий запрос не будет завершен.

  • В альтернативном решении, когда вы отправляете другой вызов Ajax с {terminate: true}, result.php может остановить обработку простым die(). Но поскольку это будет вызов async - вы не можете сопоставить его с предыдущим form submit. Так что это практически не работает.

  • Вероятное решение: отправьте форму с помощью Ajax. С помощью jQuery ajax у вас будет объект xhr, который вы можете abort() при выгрузке окна.

ОБНОВЛЕНИЕ (по комментарию):

  • Синхронный запрос - это когда ваша страница блокирует (все действия пользователя) до тех пор, пока результат не будет готов. Нажатие кнопки отправки в форме - выполните синхронный вызов на сервер, отправив форму - по определению [https://www.w3.org/TR/html-markup/button.submit.html].

  • Теперь, когда пользователь нажал кнопку отправки, соединение с браузером на сервер является синхронным - так что это не будет затруднено до тех пор, пока не появится результат. Поэтому, когда делаются другие вызовы на сервер - во время процесса отправки - ссылка на эту операцию недоступна для других, поскольку она еще не закончена. Это причина, по которой отправка терминального вызова с Ajax не будет работать.

  • В-третьих: для вашего случая вы можете рассмотреть следующий пример кода:

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>

Это пример кода - я просто использовал примеры кода и кое-что изменил здесь и там.

Ответ 3

В BaseX 8.4 была введена новая аннотация %rest:single RESTXQ, которая позволяет отменить выполняемый серверный запрос: http://docs.basex.org/wiki/RESTXQ#Query_Execution. Он должен решить хотя бы некоторые из описанных вами проблем.

Текущий способ только вернуть куски результата - передать индекс в первый и последний результат в вашем результате и выполнить фильтрацию в XQuery:

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

Вернув еще один результат, чем запрошенный, клиент будет знать, что результатов будет больше. Это может быть полезно, поскольку вычисление общего размера результата часто намного дороже, чем возврат только первых результатов.

Ответ 4

Надеюсь, я понял это правильно.

Вместо того, чтобы браузер "изначально" отправлял FORM, не записывайте JS-код, который делает это вместо этого. Другими словами (я не тестировал это, поэтому интерпретирую как псевдокод):

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

Итак, теперь, когда нажата кнопка "отправить", ничего не произойдет.

Очевидно, что вам нужна ваша форма POSTed, поэтому напишите JS, чтобы прикрепить обработчик кликов к этой кнопке отправки, собрать значения из всех полей ввода в форме (на самом деле это не так страшно, как кажется), проверьте ссылку ниже) и отправить его на сервер, сохраняя ссылку на запрос (проверьте вторую ссылку ниже), чтобы вы могли ее прервать (и, возможно, сигнализировать о выходе сервера), когда нажата кнопка отмены (в качестве альтернативы), вы можете просто отказаться от него, не заботясь о результатах).

Отправить форму с помощью jQuery

Отменить запросы Ajax с помощью jQuery

В качестве альтернативы, чтобы сделать эту разметку HTML более понятной относительно ее функциональности, подумайте над тем, чтобы не использовать FORM-тег вообще: в противном случае я предлагаю использовать его запутывание (почему оно есть, если оно не используется, знаете, я имею в виду?), Но не отвлекайтесь на это предложение, пока вы не сделаете так, как хотите; это необязательно и тема для другого дня (это может даже относиться к вашей изменяющейся архитектуре всего сайта).

ОДНАКО, вещь, о которой нужно подумать: что делать, если сообщение формы уже достигло сервера, а сервер уже начал его обрабатывать, и некоторые "мировые" изменения уже сделаны? Возможно, ваша процедура get-results не изменяет данные, так что это прекрасно. Но этот подход, вероятно, не может использоваться с POST-сообщениями с изменениями с ожиданием того, что "мир" не изменится, если нажата кнопка отмены.

Надеюсь, что это поможет:)

Ответ 5

Пользователь не должен испытывать это синхронно.

  • Клиент отправляет запрос
  • Сервер получает запрос клиента и присваивает ему идентификатор
  • Сервер "запускает" поиск и отвечает страницей нулевой информации и идентификатором поиска
  • Клиент получает страницу "placeholder" и начинает проверять, готовы ли результаты на основе идентификатора (с чем-то вроде опроса или веб-сайтов).
  • Как только поиск завершен, сервер отвечает результатами при следующем опросе (или уведомляет клиента напрямую при использовании веб-сайтов).

Это нормально, когда производительность не является узким местом, а характер обработки делает более приемлемым время ожидания. Подумайте о агрегаторах поиска рейсов, которые обычно запускаются в течение 30-90 секунд или генераторы отчетов, которые должны быть запланированы и выполняться еще дольше!

Вы можете сделать этот процесс менее расстраивающим, если вы не блокируете взаимодействие с пользователем, не обновляете его и не показываете результаты по мере их поступления.

Ответ 6

Вы должны решить эту концепцию сначала, прежде чем писать какой-либо код. Вот некоторые вещи, которые приходят вам в голову:

Что означает высвобождение ресурсов на сервере?

Что представляет собой изящное прерывание, которое освободит ресурсы?

Достаточно ли этого, чтобы убить процесс PHP, ожидающий результатов запроса? Если это так, маршрут, предложенный RandomSeed, может быть интересным. Просто имейте в виду, что он будет работать только на одном сервере. Если у вас есть несколько серверов с балансировкой нагрузки, у вас не будет способа убить процесс на другом сервере (не так легко).

Или вам нужно отменить запрос базы данных из самой базы данных? В этом случае вопрос, предложенный Кристианом Грюном, представляет большой интерес.

Или это, что нет изящного закрытия, и вы должны заставить все умереть? Если это так, это кажется ужасно взломанным.

Не все клиенты будут явно отменять

Некоторые клиенты собираются закрыть браузер, но их последняя просьба не будет проходить; некоторые клиенты потеряют подключение к Интернету и оставят службу висячей и т.д. Вы не можете получить запрос "отменить", когда клиент отключится или ушел.

Вы должны решить, следует ли жить с потенциально нежелательным поведением или осуществлять дополнительное отслеживание активного состояния, например. клиентский сервер pinging для keepalive.

Боковые заметки

  • 30 секунд или больше времени запроса потенциально долго, есть ли лучший инструмент для работы; так что вам не придется решать это с помощью такого взлома?

  • вы ищете функции параллельной системы, но вы не используете параллельную систему; если вы хотите, чтобы concurrency использовал для этого лучший инструмент/среду, например. Erlang.