multipart/form-data和FormType验证

Mal*_*chi 9 rest symfony fosrestbundle

我正在使用FOSRestBundle构建API,并且我正处于需要实现创建包含二进制数据的新实体的处理阶段.

按照发送二进制数据和REST API请求 概述的方法,multipart/form-data由于Base64需要增加约33%的带宽,因此发送数据对于我们的实现感觉最实用.

如何配置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

}
Run Code Online (Sandbox Code Playgroud)

我喜欢这种方法的原因是,我可以根据操作是更新(PUT)还是新操作(POST)来更多地控制实体的数量.

我知道Symfony的Request对象处理请求,以前JSON数据将是content变量,但现在键入request->parameters->[form key]文件bag(request->files)中的文件.

rol*_*ebi 5

似乎没有干净的方法来检索表单数据的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));
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在您的配置文件中:

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
Run Code Online (Sandbox Code Playgroud)

最后,你只需要打电话给handleRequest你的控制器.例如:

$form = $this->createFormBuilder()
    ->add('foo', 'text')
    ->add('file', 'file')
    ->getForm()
;

$form->handleRequest($request);
Run Code Online (Sandbox Code Playgroud)

使用此请求格式(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--
Run Code Online (Sandbox Code Playgroud)


Mal*_*chi 1

在放弃并寻找具有单独的图像上传端点的替代选项之后。例如:

  1. 创建新评论。

POST /comments

  1. 上传图片至终点

POST /comments/{id}/image

我发现已经有一个包提供了各种 RESTful 上传过程。其中之一是我最初希望能够multipart/form-data在提取文件时解析为实体的功能。