开发者问题收集

使用 API 平台提供静态文件

2021-03-16
2120

我正在使用 API 平台创建 API。其中一个功能是能够从独立于我的 API 开发的 React 客户端上传和下载文件

1 - 首次尝试

我按照文档设置了 VichUploaderBundle,这使我获得了与文档完全相同的配置( https://api-platform.com/docs/core/file-upload/

由此,我可以通过向订阅者设置的 contentURL 属性发送 GET 请求来获取我的图像,该请求具有以下格式:“localhost/media/{fileName}”。 但是,执行此操作时,我的应用程序会显示“CORS Missing allow origin”。

2 - 第二次尝试

我通过以下方式修复了此问题:

  • 删除订阅者和 contentUrl 属性
  • 在 get 方法上编写 itemOperation,以通过“media_objects/{id}”路由直接提供我的文件:
<?php
// api/src/Controller/GetMediaObjectAction.php

namespace App\Controller;
use App\Entity\MediaObject;

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use App\Repository\MediaObjectRepository;

final class GetMediaObjectAction
{
    private $mediaObjectRepository;

    public function __construct(MediaObjectRepository $mediaObjectRepository)
    {
        $this->mediaObjectRepository = $mediaObjectRepository;
    }

    public function __invoke(Request $request): BinaryFileResponse
    {
        $id = $request->attributes->get('id');
        $filePath = $this->mediaObjectRepository->findOneById($id)->getFilePath();
        $file = "media/" . $filePath;

        return new BinaryFileResponse($file);
    }
}

编辑: 这是我按要求实现的 MediaObject 实体

<?php
// api/src/Entity/MediaObject.php
namespace App\Entity;

use ApiPlatform\Core\Annotation\ApiProperty;
use ApiPlatform\Core\Annotation\ApiResource;
use App\Controller\CreateMediaObjectAction;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
use Vich\UploaderBundle\Mapping\Annotation as Vich;

/**
 * @ORM\Entity
 * @ApiResource(
 *     iri="http://schema.org/MediaObject",
 *     normalizationContext={
 *         "groups"={"media_object_read"}
 *     },
 *     collectionOperations={
 *         "post"={
 *             "controller"=CreateMediaObjectAction::class,
 *             "deserialize"=false,
 *             "validation_groups"={"Default", "media_object_create"},
 *             "openapi_context"={
 *                 "requestBody"={
 *                     "content"={
 *                         "multipart/form-data"={
 *                             "schema"={
 *                                 "type"="object",
 *                                 "properties"={
 *                                     "file"={
 *                                         "type"="string",
 *                                         "format"="binary"
 *                                     }
 *                                 }
 *                             }
 *                         }
 *                     }
 *                 }
 *             }
 *         },
 *         "get"
 *     },
 *     itemOperations={
 *         "get"
 *     }
 * )
 * @Vich\Uploadable
 */
class MediaObject
{
    /**
     * @var int|null
     *
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue
     * @ORM\Id
     */
    protected $id;

    /**
     * @var string|null
     *
     * @ApiProperty(iri="http://schema.org/contentUrl")
     * @Groups({"media_object_read"})
     */
    public $contentUrl;

    /**
     * @var File|null
     *
     * @Assert\NotNull(groups={"media_object_create"})
     * @Vich\UploadableField(mapping="media_object",fileNameProperty="filePath")
     */
    public $file;

    /**
     * @var string|null
     *
     * @ORM\Column(nullable=true)
     */
    public $filePath;

    public function getId(): ?int
    {
        return $this->id;
    }
}

编辑结束

现在我不再遇到此 CORS 问题,因为 API 平台在响应我的“media_objects/{id}”路由时直接提供文件。

但是,这带来了一些问题:

  • 为什么会弹出 CORS 错误首先?我猜是因为当直接在“公共”文件夹上执行获取请求时,API 平台不会强制执行其 CORS 策略,也不会向客户端提供所需的标头
  • 以这种方式提供文件是否正确?文档引入了创建 contentUrl 的订阅者这一事实让我感到疑惑……
  • 现在服务器处理在 Action 中检索文件,将文件放在公共文件夹中是否有意义?它是否允许任何人检索我的文件,并使对它们实施安全规则变得更加困难?

提前谢谢您!

1个回答

为什么一开始会弹出 CORS 错误?

因为 API 平台将 Access-Control-Allow-Origin 标头添加到 HTTP 响应(使用 Nelmio Cors Bundle ),并在 .env 文件中定义 CORS_ALLOW_ORIGIN 值。默认情况下,此值通常仅包含 localhostexample.com 。您的 React 客户端发送的请求可能不是来自这两个主机中的任何一个,导致您的浏览器介入并引发错误。更多信息请参见 这里

Nelmio Cors Bundle 配置文档 解释了如何处理此错误。最简单的方法是在 .env 中设置 CORS_ALLOW_ORIGIN=* ,并让您的 nelmio_cors.yaml 配置文件包含:

nelmio_cors:
    defaults:
        origin_regex: true
        allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']

您的自定义控制器返回的通用 BinaryFileResponse 实例不包含此标头(绕过所有 CORS 内容),从而使您的浏览器正常运行。

以这种方式提供文件是一种正确的做法吗?

我建议遵守任何供应商文档提供的指南和最佳实践。包括这一个。

将文件放在公共文件夹中是否有意义?它是否会允许任何人检索我的文件,并使对它们实施安全规则更加困难?

后端公开公共媒体资产而不是数据库 blob 没有任何问题。如果有必要,Web 服务器能够很好地限制对这些资源的访问,PHP 也一样。

Jeroen van der Laan
2021-03-16