为 <img>
提供图像
- 作者:
在本操作指南中,您将学习如何创建一个端点,用于向 HTML <img>
标签提供图像,而无需客户端 JavaScript。事实上,所介绍的技术不仅适用于提供图像,还适用于提供任意文件。
我们将从一个最小示例开始,该示例突出显示了总体概念。之后,我们将介绍一个更详细的解决方案,该解决方案修复了第一种方法的一些缺点。
警告
在数据库中保存二进制文件时要小心,在大多数情况下,最好为这些文件使用单独的存储服务。请参阅 在数据库中存储二进制文件.
最小示例
首先,我们需要一个用于存储文件的公共表。
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;
现在我们可以使用 Accept: application/octet-stream
头部请求 RPC 端点 /rpc/file?id=42
。
curl "localhost:3000/rpc/file?id=42" -H "Accept: application/octet-stream"
不幸的是,将 URL 放入 <img>
标签的 src
属性中并不会起作用。这是因为浏览器不会发送所需的 Accept: application/octet-stream
头部。相反,许多 Web 浏览器默认会发送 Accept: image/webp
头部。
幸运的是,我们可以像这样在函数中更改接受的媒体类型
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"/>
改进版本
基本解决方案有一些缺点
响应
Content-Type
头部被设置为image/webp
。如果您想为文件指定不同的格式,这可能是一个问题。对
/files?select=blob&id=eq.42
的下载请求(例如,右键单击 -> 另存为图片)将建议files
作为文件名。这可能会让用户感到困惑。对二进制端点的请求不会被缓存。这会导致数据库上的不必要负载。
以下改进版本解决了这些问题。首先,除了最小示例之外,我们需要在数据库中存储文件的媒体类型和名称。
alter table files
add column type text generated always as (byteamagic_mime(substr(blob, 0, 4100))) stored,
add column name text;
这使用了来自 pg_byteamagic 扩展 的 byteamagic_mime()
函数来自动生成 files
表中的类型。要猜测文件的类型,通常查看文件开头就足够了,这更有效率。
接下来,我们修改函数以设置内容类型和文件名。我们利用这个机会配置一些基本的客户端缓存。在生产环境中,您可能需要配置额外的缓存,例如在 反向代理 上。
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://postgresql.ac.cn/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"/>