Symfony 4 REST API: как правильно включить метод OPTIONS?
Есть клиент, есть сервер. Сервер это API, клиент это JS.
Клиент через AJAX делает запрос к API, первым запросом идет OPTIONS, но в маршрутах API прописаны только методы GET и POST (в зависимости от запроса), но по всем маршрутам сначала идет запрос OPTIONS, который конечно же не проходит проверку.
Просто добавив метод OPTIONS во все маршруты решает проблему, но это не выход, ведь в этом случае полностью обрабатываются оба запроса OPTIONS + GET что нехорошо, т.к. OPTIONS не должен ничего выполнять и просто должен вернуть одни заголовки с пустым ответом.
Добавление в заголовках к response Access-Control-Allow-Methods: GET, POST, OPTIONS не дает результата
Дмитрий Щербаков дорогой пользователь, настоятельно рекомендуем еще раз обратить самое пристальное внимание на п. 3.1 регламента работы сервиса (и, в особенности, на его последний абзац).
В противном случае, ваши вопросы будут удаляться по причине тег-спама, а систематические нарушения приведут к блокировке учетной записи.
voronkovich я не использую маршруты в виде нотаций, у меня YAML файл
можно автоматизировать OPTIONS и вынести его в блок когда уже сработал URLMatcher и нашел необходимый маршрут, но перед запуском необходимого контроллера, тогда не нужно будет в каждом контроллере обрабатывать OPTIONS
if ($request->getMethod() == 'OPTIONS') {
$response = new Response();
$response->headers->set('Access-Control-Allow-Methods', 'OPTIONS, ' . $this->request->headers->get('access-control-request-method'));
$response->send();
} else {
// запускается поиск контроллера и его выполнение
}
Дмитрий Щербаков, Если вы хотите конфигурировать OPTIONS для нескольких маршрутов, рекомендую создать отдельный контроллер для этих целей:
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
class OptionsController extends AbstractController
{
public function __invoke(Request $request, string $allowedMethods = ''): Response
{
if (!$request->isMethod('OPTIONS')) {
throw new MethodNotAllowedHttpException();
}
$response = new Response();
$response->headers->set('Allow', 'OPTIONS, '.$allowedMethods);
return $response;
}
}
voronkovich интересное решение, спасибо. Но мне надо сразу ВСЕ маршруты с OPTIONS так что мне хватает одной проверки в 3 строчки кода. Причем никогда не понадобится делать маршрут без OPTIONS т.к. это внутреннее API и доступ только через CORS
Дмитрий Щербаков, Ну тогда я могу предложить вам идеальное решение :)
Когда маршрут есть, но не принимает HTTP-метод (OPTIONS, в вашем случае) маршрутизатор бросает исключение: MethodNotAllowedHttpException.
Когда бросается исключение, HttpKernel запускает событие 'kernel.exception'. Можно сделать подписчик на это событие, который будет возвращать нужный ответ для запроса OPTIONS:
<?php
namespace App\EventSubscriber;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\KernelEvents;
class ApiOptionsSubscriber implements EventSubscriberInterface
{
public function onKernelException(GetResponseForExceptionEvent $event)
{
$exception = $event->getException();
if (!$exception instanceof MethodNotAllowedHttpException) {
return;
}
$request = $event->getRequest();
if (!$request->isMethod('OPTIONS') && 0 !== strpos($request->getUri(), '/api/')) {
return;
}
$allowedMethods = 'OPTIONS, '.$exception->getHeaders()['Allow'];
$response = new Response();
$response->headers->set('Access-Control-Allow-Methods', $allowedMethods);
$event->setResponse($response);
$event->allowCustomResponseCode();
}
public static function getSubscribedEvents()
{
return [
KernelEvents::EXCEPTION => 'onKernelException',
];
}
}
voronkovich, вон во втором ответе я писал комментарий про ненужное усложнение )) зачем настолько усложнять, если достаточно добавить одно условие в 3 строчки кода перед вызовом контроллера, а тут уже целый класс появился.
Код усложняется, порог входа в проект увеличивается, если для Москвы (например) это нормально т.к. больший выбор программистов, то чем меньше город тем гораздо сложнее найти высококлассных программистов, а тут еще и умеющих в ивент-программирование.
Дмитрий Щербаков, Ну, вообще говоря, вы правы. Если понадобится что-то сложнее лучше сразу взять CorsBundle, а не писать свои костыли :) Пока хватает 3-х строчек, пусть будут 3 строчки.
Дмитрий Щербаков, тогда советую посмотреть, что в ней реализуется (CORS - это немного бoльшее, чем просто один заголовок отправить), и сделать самому глобальный роут.
Mikhail Osher ну да. это понятно, просто не хочется усложнять там где это не нужно, API не публичный, а внутренний, к нему написана документация. Добавив 3 строчки кода я решил свою проблему навсегда.
P.S. Чрезмерное усложнение это боль в последнее время (боль для клиентов, потом в будущем искать замену программисту кто все это писал), например на одном проекте где обычный CRUD зачем-то используется БД Apache Cassandra, причем в проекте в базу идут от силы 2-3 записи в день.
Mikhail Osher, в моем случае сложно, я не использую Symfony Framework целиком, я использую только отдельные компоненты, а если в будущем понадобится кастомизация тогда и надо будет подключать