使用 API 平台提供静态文件
我正在使用 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 中检索文件,将文件放在公共文件夹中是否有意义?它是否允许任何人检索我的文件,并使对它们实施安全规则变得更加困难?
提前谢谢您!
为什么一开始会弹出 CORS 错误?
因为 API 平台将
Access-Control-Allow-Origin
标头添加到 HTTP 响应(使用
Nelmio Cors Bundle
),并在
.env
文件中定义
CORS_ALLOW_ORIGIN
值。默认情况下,此值通常仅包含
localhost
和
example.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 也一样。