multipart / form-data和FormType验证

时间:2014-08-15 13:53:56

标签: rest symfony fosrestbundle

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

根据Sending binary data along with a REST API request 上列出的方法,multipart/form-data发送数据对我们的实施感到最实用,因为Base64需要增加约33%的带宽。

问题

如何配置REST端点以处理请求中的文件,并在以multipart/form-data发送数据时对JSON编码实体执行验证?

当发送原始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]以及文件包中的文件({ {1}})。

4 个答案:

答案 0 :(得分: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));
    }
}

然后在你的配置文件中:

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--

答案 1 :(得分:1)

以下是更明确的解决方案:http://labs.qandidate.com/blog/2014/08/13/handling-angularjs-post-requests-in-symfony/

  

将此代码复制并粘贴到其他控制器是非常WET,我们喜欢DRY!

     

如果我告诉您,您可以将其应用于每个JSON请求而不必担心怎么办?我们>写了一个事件监听器 - 当标记为kernel.event_listener时 - 将:

     

检查请求是否是JSON请求   如果是这样,解码JSON   填充Request :: $ request对象   出现问题时返回HTTP 400错误请求。   查看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>

答案 2 :(得分:1)

放弃并查看为图像上传设置单独端点的替代选项。例如:

  1. 创建新评论。
  2. POST /comments

    1. 将图片上传到终点
    2. POST /comments/{id}/image

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

答案 3 :(得分:0)

修改应用以在JSON中发送文件内容。

  1. 阅读您应用中的文件内容。
  2. Base64编码文件的内容
  3. 使用您的所有字段(包括具有文件内容的字段)创建JSON
  4. JSON发送到服务器。
  5. 以标准方式处理它。
  6. 您可以使用base64编码的字符串获取文件内容。然后,您可以解码并验证它。

    您的JSON将如下所示:

    {
        name: 'Foo',
        phone: '123.345.678',
        profile_image: 'R0lGODlhAQABAJAAAP8AAAAAACH5BAUQAAAALAAAAAABAAEAAAICBAEAOw=='
    }