Ранний ответ

oxphp_finish_request() немедленно отправляет клиенту полный HTTP-ответ и позволяет вашему PHP-скрипту продолжить работу для выполнения фоновых задач. Это аналог fastcgi_finish_request() из PHP-FPM в OxPHP.

Как это работает

  1. Формирование ответа. Ваш скрипт как обычно устанавливает заголовки, код состояния и выводит тело ответа через echo.
  2. Завершение запроса. Вызовите oxphp_finish_request(). OxPHP сбрасывает все буферы вывода, помечает запрос как завершённый и доставляет клиенту полный HTTP-ответ.
  3. Выполнение фоновой работы. Скрипт продолжает выполняться для фоновых задач, таких как отправка писем, запись записей в кэш или рассылка вебхуков.
  4. Последующий вывод отбрасывается. Любой вывод, сформированный после вызова (echo, print, var_dump), молча отбрасывается.
  5. Вызов идемпотентен. oxphp_finish_request() возвращает true при первом вызове и false при любом последующем вызове в рамках того же запроса.

Сценарии использования

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

  • Отправка писем — сразу верните «принято» и отправьте письмо в фоне
  • Прогрев кэша — ответьте закэшированными данными, а затем перегенерируйте запись кэша
  • Аналитика и логирование — подтвердите запрос, а затем запишите подробные аналитические записи
  • Рассылка вебхуков — подтвердите получение вызывающей стороне, а затем разошлите доставки вебхуков
  • Обработка изображений — сразу верните URL, а затем обработайте полноразмерное изображение

Примеры на PHP

Базовое использование

php
<?php header('Content-Type: application/json'); echo json_encode(['status' => 'accepted', 'id' => uniqid()]); // Send response now — client receives the full response at this point oxphp_finish_request(); // Background work runs here; client is no longer waiting file_put_contents('/tmp/audit.log', date('c') . " request processed\n", FILE_APPEND); send_notification_email($user);

Защита от двойного вызова

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

php
<?php function finish_and_cleanup(): void { if (!oxphp_finish_request()) { // Already finished — background work was already scheduled return; } // First call — safe to run cleanup flush_metrics_buffer(); close_external_connections(); }

Условная фоновая работа

php
<?php header('Content-Type: application/json'); $payload = json_decode(file_get_contents('php://input'), true); $result = handle_request($payload); echo json_encode($result); if ($result['needs_sync']) { oxphp_finish_request(); sync_to_external_service($result); } // No early finish if sync is not needed — script exits normally

Режим воркеров

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

php
<?php oxphp_worker(function () { $order = json_decode(file_get_contents('php://input'), true); $result = process_order($order); header('Content-Type: application/json'); echo json_encode(['order_id' => $result['id'], 'status' => 'accepted']); oxphp_finish_request(); // Worker is still occupied during this background work send_confirmation_email($result); update_inventory($result); notify_warehouse($result); // Worker becomes available after this point });
Note

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

Устранение неполадок

Фоновая работа не завершается

PHP-настройка max_execution_time продолжает действовать и после вызова oxphp_finish_request(). Если суммарное время выполнения скрипта, включая фоновую работу, превышает лимит, запрос отменяется с фатальной ошибкой Request cancelled (timeout).

Решение: увеличьте max_execution_time (в php.ini или через set_time_limit() из скрипта) либо переместите длительные фоновые задачи в очередь сообщений:

php
set_time_limit(300); oxphp_finish_request(); // ... long-running work ...

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

Сессию нельзя запустить после того, как ответ ушёл

oxphp_finish_request() отправляет ответ в том виде, в каком он есть (код состояния, заголовки и тело), и всё, что добавлено в него позже, уже не отправляется. Отправка ответа означает и то, что заголовки уже отправлены, а после этого PHP отказывается запускать сессию: session_start() выдаёт Session cannot be started after headers have already been sent и возвращает false, и session_regenerate_id() отказывает точно так же. Сессия не запускается вовсе, поэтому записи в $_SESSION после этого момента попадают в обычный массив, который никто не сохраняет. Во фреймворке, превращающем предупреждения в исключения, этот отказ становится исключением, выброшенным внутри запроса, на который уже ответили.

Решение: запускайте сессию до вызова и закрывайте её тоже до вызова, чтобы запись и снятие блокировки происходили в момент, который вы контролируете:

php
<?php session_start(); // Before the response goes out, not after $_SESSION['last_seen'] = time(); session_write_close(); // Persist the session and release the save handler's lock oxphp_finish_request(); // Send response // ... work that no longer has a session ...

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

Тело ответа пустое после вызова oxphp_finish_request()

Если вы вызываете oxphp_finish_request() до какого-либо вывода через echo, клиент получает пустое тело. Сначала сформируйте и выведите ответ, а затем вызывайте функцию.

Примечания

  • oxphp_finish_request() возвращает true при первом вызове и false при последующих вызовах в рамках того же запроса.
  • Весь вывод (echo, print, var_dump) после первого вызова молча отбрасывается.
  • В режиме воркеров воркер остаётся занятым до полного завершения колбэка, включая весь код после ответа.
  • Таймаут запроса продолжает применяться к фоновому коду, выполняемому после oxphp_finish_request().
  • oxphp_finish_request() и oxphp_stream_flush() взаимоисключающи: вызов oxphp_finish_request() до начала стрима не даёт запустить стриминг, а вызов после oxphp_stream_flush() закрывает стрим.

Промпты для вашего агента

Эти промпты рассчитаны на агента с подключённым MCP-сервером документации OxPHP — нужные страницы он прочитает сам.

prompt
Убери работу, которая идёт после ответа, за `oxphp_finish_request()`. Прочитай страницу `features/early-response` через MCP-сервер документации OxPHP. Найди в этом репозитории работу, которую клиент ждёт, но которая ему не нужна (письма, рассылку вебхуков, запись аналитики), и покажи эти обработчики переписанными так, чтобы ответ уходил первым, а любая сессия запускалась и закрывалась до вызова.

См. также

  • Режим воркеров — постоянные PHP-воркеры и то, как ранний ответ взаимодействует с циклом обработки запросов
  • Таймауты — как таймаут запроса применяется к фоновой работе
  • PHP-функции — полный справочник по oxphp_finish_request() и другим встроенным функциям
Нашли ошибку? Сообщите →