媒体类型处理程序

媒体类型处理程序允许 PostgREST 提供自定义媒体类型。这些处理程序扩展了 内置类型,也可以覆盖它们。

媒体类型使用 表示为类型别名,其名称必须符合 RFC 6838 要求

CREATE DOMAIN "application/json" AS json;

使用这些域,函数 可以成为处理程序,而 用户定义的聚合函数 可以作为 表和视图 以及 表值函数 的处理程序。

重要

  • PostgREST 供应商媒体类型(application/vnd.pgrst.planapplication/vnd.pgrst.objectapplication/vnd.pgrst.array)不可覆盖。

  • application/vnd.openxmlformats-officedocument.wordprocessingml.document 这样的长媒体类型不能表示为域,因为它们超过了 PostgreSQL 标识符长度。对于这些,您可以使用 “任何” 处理程序

处理程序函数

例如,让我们获取 PostGIS 几何图形的 TWKB 压缩二进制格式。

create extension postgis;

create table lines (
  id   int primary key
, name text
, geom geometry(LINESTRING, 4326)
);

insert into lines values (1, 'line-1', 'LINESTRING(1 1,5 5)'::geometry), (2, 'line-2', 'LINESTRING(2 2,6 6)'::geometry);

为此,您可以创建一个供应商媒体类型。

create domain "application/vnd.twkb" as bytea;

并将其用作函数的返回类型,使其成为处理程序。

create or replace function get_line (id int)
returns "application/vnd.twkb" as $$
  select st_astwkb(geom) from lines where id = get_line.id;
$$ language sql;

注意

对于 PostgreSQL <= 12,您需要在函数体上进行强制转换 st_astwkb(geom)::"application/vnd.twkb"

现在您可以像这样请求 TWKB 输出

curl 'localhost:3000/rpc/get_line?id=1' -i \
  -H "Accept: application/vnd.twkb"

HTTP/1.1 200 OK
Content-Type: application/vnd.twkb

# binary output

请注意,PostgREST 会自动将 Content-Type 设置为 application/vnd.twkb

表/视图的处理程序

为了从像 TWKB 这样的压缩格式中获益,获取多行而不是一行更有意义。让我们通过为该表添加一个处理程序来允许这样做。

用户定义的聚合函数可以通过使用域媒体类型作为其转换函数或最终函数的返回类型来转换为处理程序。

让我们为这个例子创建一个转换函数。

create or replace function twkb_handler_transition (state bytea, next lines)
returns "application/vnd.twkb" as $$
  select state || st_astwkb(next.geom);
$$ language sql;

现在,我们将它用于为 lines 表定义的新聚合函数。

create or replace aggregate twkb_agg (lines) (
  initcond = ''
, stype = "application/vnd.twkb"
, sfunc = twkb_handler_transition
);

注意

您可以测试查看此聚合函数是否有效

SELECT twkb_agg(l) from lines l;

                           twkb_agg
---------------------------------------------------------------
\xa20002c09a0cc09a0c80ea3080ea30a2000280b51880b51880ea3080ea30
(1 row)

现在,您可以使用 twkb 媒体类型请求表端点

curl 'localhost:3000/lines' -i \
  -H "Accept: application/vnd.twkb"

HTTP/1.1 200 OK
Content-Type: application/vnd.twkb

# binary output

如果您的表值函数返回相同类型的表,则处理程序也可以对其进行操作。

create or replace function get_lines ()
returns setof lines as $$
  select * from lines;
$$ language sql;
curl 'localhost:3000/get_lines' -i \
  -H "Accept: application/vnd.twkb"

HTTP/1.1 200 OK
Content-Type: application/vnd.twkb

# binary output

覆盖内置处理程序

让我们覆盖现有的 text/csv 处理程序,以便为表提供更复杂的 CSV 输出。它将包括一个 字节顺序标记 (BOM) 以及一个 Content-Disposition 标头,用于设置下载文件的名称。

为标准 text/csv 媒体类型创建一个域。

create domain "text/csv" as text;

以及一个返回该域的转换函数。

create or replace function bom_csv_trans (state text, next lines)
returns "text/csv" as $$
  select state || next.id::text || ',' || next.name || ',' || next.geom::text || E'\n';
$$ language sql;

这次我们将添加一个最终函数。这将添加 CSV 标头、BOM 和 Content-Disposition 标头。

create or replace function bom_csv_final (data "text/csv")
returns "text/csv" as $$
  -- set the Content-Disposition header
  select set_config('response.headers', '[{"Content-Disposition": "attachment; filename=\"lines.csv\""}]', true);
  select
    -- EFBBBF is the BOM in UTF8 https://en.wikipedia.org/wiki/Byte_order_mark#UTF-8
    convert_from (decode (E'EFBBBF', 'hex'),'UTF8') ||
    -- the header for the CSV
    (E'id,name,geom\n' || data);
$$ language sql;

现在将转换函数和最终函数用作新聚合的一部分。

create or replace aggregate bom_csv_agg (lines) (
  initcond = ''
, stype = "text/csv"
, sfunc = bom_csv_trans
, finalfunc = bom_csv_final
);

注意

您可以使用以下方法进行测试

select bom_csv_agg(l) from lines l;
                                             bom_csv_agg
-----------------------------------------------------------------------------------------------------
 id,name,geom                                                                                      +
 1,line-1,0102000020E610000002000000000000000000F03F000000000000F03F00000000000014400000000000001440+
 2,line-2,0102000020E6100000020000000000000000000040000000000000004000000000000018400000000000001840+

(1 row)

并像这样请求它

curl 'localhost:3000/lines' -i \
  -H "Accept: text/csv"

HTTP/1.1 200 OK
Content-Type: text/csv
Content-Disposition: attachment; filename="lines.csv"

id,name,geom
1,line-1,0102000020E610000002000000000000000000F03F000000000000F03F00000000000014400000000000001440
2,line-2,0102000020E6100000020000000000000000000040000000000000004000000000000018400000000000001840

“任何”处理程序

为了提高灵活性,您还可以通过使用名为 */*(任何媒体类型)的域来定义一个通配符处理程序。此处理程序遵循以下规则

  • 它响应所有媒体类型,甚至响应不包含 Accept 标头的请求。

  • 默认情况下,它将 Content-Type 标头设置为 application/octet-stream,但这可以在函数内部使用 响应标头 覆盖。

  • 它覆盖所有其他处理程序(内置 或自定义),因此最好将其用于隔离的函数或视图。

让我们为一个始终以 XML 输出响应的视图定义一个任何处理程序。它将接受 text/xmlapplication/xml*/* 并拒绝其他媒体类型。

create domain "*/*" as bytea;

-- we'll use an .xml suffix for the view to be clear its output is always XML
create view "lines.xml" as
select * from lines;

-- transition function
create or replace function lines_xml_trans (state "*/*", next "lines.xml")
returns "*/*" as $$
  select state || xmlelement(name line, xmlattributes(next.id as id, next.name as name), next.geom)::text::bytea || E'\n' ;
$$ language sql;

-- final function
create or replace function lines_xml_final (data "*/*")
returns "*/*" as $$
declare
  -- get the Accept header
  req_accept text := current_setting('request.headers', true)::json->>'accept';
begin
  -- when we need to override the default Content-Type (application/octet-stream) set by PostgREST
  if req_accept = '*/*' then
    perform set_config('response.headers', json_build_array(json_build_object('Content-Type', 'text/xml'))::text, true);
  elsif req_accept IN ('application/xml', 'text/xml') then
    perform set_config('response.headers', json_build_array(json_build_object('Content-Type', req_accept))::text, true);
  else
    -- we'll reject other non XML media types, we need to reject manually since */* will command PostgREST to accept all media types
    raise sqlstate 'PT415' using message = 'Unsupported Media Type';
  end if;

  return data;
end; $$ language plpgsql;

-- new aggregate
create or replace aggregate lines_xml_agg ("lines.xml") (
  stype = "*/*"
, sfunc = lines_xml_trans
, finalfunc = lines_xml_final
);

在 SQL 上测试它

select (encode(lines_xml_agg(x), 'escape'))::xml from "lines.xml" x;
                                                            encode
------------------------------------------------------------------------------------------------------------------------------
 <line id="1" name="line-1">0102000020E610000002000000000000000000F03F000000000000F03F00000000000014400000000000001440</line>+
 <line id="2" name="line-2">0102000020E6100000020000000000000000000040000000000000004000000000000018400000000000001840</line>+

现在我们可以省略 Accept 标头,它将以 XML 响应。

curl 'localhost:3000/lines.xml' -i

HTTP/1.1 200 OK
Content-Type: text/xml

<line id="1" name="line-1">0102000020E610000002000000000000000000F03F000000000000F03F00000000000014400000000000001440</line>
<line id="2" name="line-2">0102000020E6100000020000000000000000000040000000000000004000000000000018400000000000001840</line>

它将只接受 XML 媒体类型。

curl 'localhost:3000/lines.xml' -i \
  -H "Accept: text/xml"

HTTP/1.1 200 OK
Content-Type: text/xml
curl 'localhost:3000/lines.xml' -i  \
  -H "Accept: application/xml"

HTTP/1.1 200 OK
Content-Type: text/xml
curl 'localhost:3000/lines.xml' -i \
  -H "Accept: unknown/media"

HTTP/1.1 415 Unsupported Media Type