Проверка данных multipart/form-data и FormType
Я создаю API с использованием FOSRestBundle и нахожусь на этапе, когда мне нужно реализовать обработку создания новых объектов, которые содержат двоичные данные.
Следуя методам, описанным в разделе Отправка двоичных данных вместе с запросом REST API, отправка данных в виде multipart/form-data
кажется наиболее практичным для нашей реализации из-за ~33% дополнительной пропускной способности, необходимой для Base64.
Вопрос
Как я могу настроить конечную точку REST для обработки файла в запросе и для проверки объекта в кодировке JSON при отправке данных как multipart/form-data
?
Когда я просто отправлял сырой JSON, я использовал форму Symfony handleRequest
метод для проверки по обычаю FormType
, Например:
$form = $this->createForm(new CommentType(), $comment, ['method' => 'POST']);
$form->handleRequest($request);
if ($form->isValid()) {
// Is valid
}
Причина, по которой мне нравится этот подход, заключается в том, что я могу больше контролировать заполнение сущности в зависимости от того, является ли действие обновлением (PUT) или новым (POST).
Я понимаю, что Symfony Request
объект обрабатывает запрос так, что ранее данные JSON были бы content
переменная, но теперь имеет ключ request->parameters->[form key]
и файлы в папке с файлами (request->files
).
4 ответа
После того, как вы отказались и посмотрели на альтернативный вариант иметь отдельную конечную точку для загрузки изображения. Например:
- Создайте новый комментарий.
POST /comments
- Загрузить изображение в конечную точку
POST /comments/{id}/image
Я обнаружил, что уже есть пакет, который обеспечивает различные процессы загрузки RESTful. Одним из которых был тот, который я изначально хотел иметь возможность анализировать multipart/form-data
в сущности при извлечении файла.
Кажется, что нет чистого способа получить Content-Type данных формы без анализа необработанного запроса.
Если ваш API поддерживает только ввод json или вы можете добавить собственный заголовок (см. Комментарии ниже), вы можете использовать это решение:
Сначала вы должны реализовать свой собственный body_listener
:
namespace Acme\ApiBundle\FOS\EventListener;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use FOS\RestBundle\Decoder\DecoderProviderInterface;
class BodyListener
{
/**
* @var DecoderProviderInterface
*/
private $decoderProvider;
/**
* @param DecoderProviderInterface $decoderProvider Provider for fetching decoders
*/
public function __construct(DecoderProviderInterface $decoderProvider)
{
$this->decoderProvider = $decoderProvider;
}
/**
* {@inheritdoc}
*/
public function onKernelRequest(GetResponseEvent $event)
{
$request = $event->getRequest();
if (strpos($request->headers->get('Content-Type'), 'multipart/form-data') !== 0) {
return;
}
$format = 'json';
/*
* or, using a custom header :
*
* if (!$request->headers->has('X-Form-Content-Type')) {
* return;
* }
* $format = $request->getFormat($request->headers->get('X-Form-Content-Type'));
*/
if (!$this->decoderProvider->supports($format)) {
return;
}
$decoder = $this->decoderProvider->getDecoder($format);
$iterator = $request->request->getIterator();
$request->request->set($iterator->key(), $decoder->decode($iterator->current(), $format));
}
}
Затем в вашем конфигурационном файле:
services:
acme.api.fos.event_listener.body:
class: Acme\ApiBundle\FOS\EventListener\BodyListener
arguments:
- "@fos_rest.decoder_provider"
tags:
-
name: kernel.event_listener
event: kernel.request
method: onKernelRequest
priority: 10
Наконец, вам просто нужно позвонить handleRequest
в вашем контроллере. Пример:
$form = $this->createFormBuilder()
->add('foo', 'text')
->add('file', 'file')
->getForm()
;
$form->handleRequest($request);
Используя этот формат запроса (form
необходимо заменить на имя вашей формы):
POST http://xxx.xx HTTP/1.1
Content-Type: multipart/form-data; boundary="01ead4a5-7a67-4703-ad02-589886e00923"
Host: xxx.xx
Content-Length: XXX
--01ead4a5-7a67-4703-ad02-589886e00923
Content-Type: application/json; charset=utf-8
Content-Disposition: form-data; name=form
{"foo":"bar"}
--01ead4a5-7a67-4703-ad02-589886e00923
Content-Type: text/plain
Content-Disposition: form-data; name=form[file]; filename=foo.txt
XXXX
--01ead4a5-7a67-4703-ad02-589886e00923--
Вот более понятное решение: http://labs.qandidate.com/blog/2014/08/13/handling-angularjs-post-requests-in-symfony/
Скопируйте и вставьте этот код в другие контроллеры, очень влажно, и нам нравится DRY!
Что если я скажу вам, что вы можете применить это к каждому запросу JSON, не беспокоясь об этом? Мы> написали прослушиватель событий, который - когда помечен как kernel.event_listener - будет:
проверьте, является ли запрос JSON-запросом, если это так, декодируйте JSON, заполните объект Request::$request и верните HTTP 400 Bad Request, если что-то пошло не так. Проверьте код на https://github.com/qandidate-labs/symfony-json-request-transformer! Регистрация слушателя этого события действительно проста. Просто добавьте следующее в ваш services.xml:
<service id="kernel.event_listener.json_request_transformer" > class="Qandidate\Common\Symfony\HttpKernel\EventListener\JsonRequestTransformerListener"> <tag name="kernel.event_listener" event="kernel.request" method="onKernelRequest" priority="100" /> </service>
Измените приложение для отправки содержимого файла в формате JSON.
- Прочитайте содержимое файла в вашем приложении.
- Base64 кодирует содержимое файла
- Создать
JSON
со всем вашим полем (в том числе с содержанием файла) - Отправить
JSON
на сервер. - Обрабатывать это стандартным способом.
Вы получаете содержимое файла в кодированной строке base64. Затем вы можете расшифровать и проверить его.
Ваш JSON
будет выглядеть так:
{
name: 'Foo',
phone: '123.345.678',
profile_image: 'R0lGODlhAQABAJAAAP8AAAAAACH5BAUQAAAALAAAAAABAAEAAAICBAEAOw=='
}