为以下项目提供图像 <img>#

作者:

pkel

在本操作指南中,您将学习如何创建用于将图像提供给HTML的终结点 <img> 没有客户端Java脚本的标签。事实上,所提出的技术不仅适用于提供图像,而且适用于提供任意文件。

我们将从一个突出一般概念的最小示例开始。之后,我们将提供一个更详细的解决方案,以修复第一种方法的一些缺点。

警告

在数据库中保存二进制文件时要小心,在大多数情况下,最好为这些文件提供单独的存储服务。看见 Storing Binary files in the Database

最小示例#

首先,我们需要一个公用表来存储文件。

create table files(
  id   int primary key
, blob bytea
);

让我们假设这张表包含两只ID为42的可爱小猫的图像。我们可以使用以下命令从PostgREST API中检索二进制格式的图像 媒体类型处理程序

create domain "application/octet-stream" as bytea;

create or replace function file(id int) returns "application/octet-stream" as $$
  select blob from files where id = file.id;
$$ language sql;

现在我们可以请求RPC端点 /rpc/file?id=42Accept: application/octet-stream 标题。

curl "localhost:3000/rpc/file?id=42" -H "Accept: application/octet-stream"

不幸的是,将URL放入 src 属于一个 <img> 标记将不起作用。这是因为浏览器不会发送所需的 Accept: application/octet-stream 标题。相反, Accept: image/webp 默认情况下,许多Web浏览器都会发送Header。

幸运的是,我们可以更改函数中接受的媒体类型,如下所示:

create domain "image/webp" as bytea;

create or replace function file(id int) returns "image/webp" as $$
  select blob from files where id = file.id;
$$ language sql;

现在,该图像将显示在HTML页面中:

<img src="http://localhost:3000/file?id=42" alt="Cute Kittens"/>

改进版#

基本解决方案有一些缺点:

  1. 他们的回应 Content-Type 标头设置为 image/webp 。如果要为文件指定不同的格式,这可能是一个问题。

  2. 下载请求(例如,右击->图像另存为)到 /files?select=blob&id=eq.42 将会提议 files 作为文件名。这可能会让用户感到困惑。

  3. 不缓存对二进制终结点的请求。这将对数据库造成不必要的负载。

以下改进版本解决了这些问题。首先,除了最小的示例之外,我们还需要在数据库中存储文件的媒体类型和名称。

alter table files
  add column type text generated always as (byteamagic_mime(substr(blob, 0, 4100))) stored,
  add column name text;

这使用了 byteamagic_mime() 函数从 pg_byteamagic extension 若要在 files 桌子。要猜测文件的类型,通常只需查看文件的开头就足够了,这样效率更高。

接下来,我们设置Modify函数来设置内容类型和文件名。我们利用这个机会配置一些基本的客户端缓存。对于生产环境,您可能需要配置其他缓存,例如在 reverse proxy

create domain "*/*" as bytea;

create function file(id int) returns "*/*" as
$$
  declare headers text;
  declare blob bytea;
  begin
    select format(
      '[{"Content-Type": "%s"},'
       '{"Content-Disposition": "inline; filename=\"%s\""},'
       '{"Cache-Control": "max-age=259200"}]'
      , files.type, files.name)
    from files where files.id = file.id into headers;
    perform set_config('response.headers', headers, true);
    select files.blob from files where files.id = file.id into blob;
    if FOUND -- special var, see https://www.postgresql.org/docs/current/plpgsql-statements.html#PLPGSQL-STATEMENTS-DIAGNOSTICS
    then return(blob);
    else raise sqlstate 'PT404' using
      message = 'NOT FOUND',
      detail = 'File not found',
      hint = format('%s seems to be an invalid file id', file.id);
    end if;
  end
$$ language plpgsql;

这样,我们就可以从 /rpc/file?id=42 。因此,生成的HTML将是:

<img src="http://localhost:3000/rpc/file?id=42" alt="Cute Kittens"/>