PostgREST是构建RESTful API的快速方法。它的默认行为非常适合开发中的脚手架。只要您采取预防措施,当需要投入生产时,它也会发挥很大的作用。PostgREST是一个小巧的工具,专注于执行API到数据库的映射。我们依靠像Nginx这样的反向代理来提供其他保护措施。
第一步是创建一个Nginx配置文件,该文件代理对基础PostgREST服务器的请求。
http { # ... # upstream configuration upstream postgrest { server localhost:3000; } # ... server { # ... # expose to the outside world location /api/ { default_type application/json; proxy_hide_header Content-Location; add_header Content-Location /api/$upstream_http_content_location; proxy_set_header Connection ""; proxy_http_version 1.1; proxy_pass http://postgrest/; } # ... } }
注意
对于ubuntu,如果您已经通过安装了nginx,则apt
可以将其添加到中的配置文件中 /etc/nginx/sites-enabled/default
。
管理员选择的架构中的每个表都作为顶级路由公开。客户端请求由某些数据库角色执行,具体取决于它们的身份验证。支持所有与角色允许的动作相对应的HTTP动词。例如,如果活动角色可以删除表的行,则客户端可以使用DELETE动词。这是一个API要求,用于从假设的日志表中删除旧行:
DELETE /logs?time=lt.1991-08-06 HTTP/1.1
但是,通过省略查询参数来删除整个表非常容易!
DELETE /logs HTTP/1.1
这可能是偶然发生的,例如通过将请求从GET切换到DELETE。为了防止意外操作,请使用pg-safeupdate PostgreSQL扩展。如果在未指定条件的情况下执行UPDATE或DELETE,则会引发错误。要安装它,您可以使用PGXN网络:
sudo -E pgxn install safeupdate # then add this to postgresql.conf: # shared_preload_libraries='safeupdate';
这不能防止恶意行为,因为有人可以添加不影响结果集的url参数。为避免这种情况,您必须求助于数据库权限,禁止错误的人删除行,并且在需要更好的访问控制时使用行级安全性。
为了方便客户端分页控件,PostgREST支持在响应中计数和报告表的总大小。如“ 限制和分页”中所述,响应通常包括一个范围,但未指定总计,例如
HTTP/1.1 200 OK Range-Unit: items Content-Range: 0-14/*
但是,包括请求标头将计算并包括全部计数:Prefer: count=exact
HTTP/1.1 206 Partial Content Range-Unit: items Content-Range: 0-14/3573458
这在小型表中很好,但是由于PostgreSQL的MVCC体系结构,大型表的计数性能会下降。对于非常大的表,检索结果可能花费很长时间,这会导致拒绝服务攻击。解决方案是从所有请求中删除此标头:
-- Pending nginx config: Remove any prefer header which contains the word count
注意
在将来的版本中,我们将支持利用PostgreSQL统计表来获得快速(且相当准确)的结果。Prefer: count=estimated
请参阅身份验证指南的HTTPS部分。
Nginx支持“泄漏桶”速率限制(请参阅官方文档)。使用标准的Nginx配置,可以将路由分组到请求区域中以进行速率限制。例如,我们可以为登录尝试定义一个区域:
limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s;
这将创建一个名为“登录”的共享内存区域,以存储访问限速网址的IP地址日志。保留的10 MB(10m
)空间将为我们提供足够的空间来存储160k请求的历史记录。我们已选择每秒仅允许一个请求(1r/s
)。
接下来,我们将该区域应用于某些路由,例如称为的假设存储过程login
。
location /rpc/login/ { # apply rate limiting limit_req zone=login burst=5; }
如果来自特定IP的队列超过五个,则burst参数告诉Nginx开始丢弃请求。
Nginx速率限制是通用且不加区分的。要对每个已认证的请求分别进行速率限制,您将需要在“ 自定义验证”功能中添加逻辑。
调试问题时,验证PostgREST版本很重要。您可以随时向运行中的服务器发出请求,并确切确定要部署的版本。查找Server
HTTP响应标头,其中包含版本号。
PostgREST服务器将基本请求信息记录到stdout,包括请求的IP地址和用户代理,请求的URL和HTTP响应状态。但是,这为调试服务器错误提供了有限的信息。获取有关客户端请求和针对基础数据库执行的相应SQL命令的完整信息很有帮助。
检查包括标头和查询参数的传入HTTP请求的一种好方法是在运行PostgREST的端口上嗅探网络流量。例如,在绑定到本地主机上的端口3000的开发服务器上,运行以下命令:
# sudo access is necessary for watching the network sudo ngrep -d lo0 port 3000
ngrep的选项取决于绑定服务器的地址和主机。绑定在“ 配置”部分中进行了描述。ngrep的输出不是特别漂亮,但是很清晰。
确认请求符合您的期望后,您可以通过查看数据库日志来获取有关服务器操作的更多信息。默认情况下,PostgreSQL不保留这些日志,因此您需要在下面进行配置更改。寻找postgresql.conf
你的PostgreSQL数据目录中(地发现,发出命令)。找到分散在整个文件中的设置并将其更改为以下值,或者将此代码块附加到配置文件的末尾。show data_directory;
# send logs where the collector can access them log_destination = "stderr" # collect stderr output to log files logging_collector = on # save logs in pg_log/ under the pg data directory log_directory = "pg_log" # (optional) new log file per day log_filename = "postgresql-%Y-%m-%d.log" # log every kind of SQL statement log_statement = "all"
重新启动数据库并实时查看日志文件,以了解如何将HTTP请求转换为SQL命令。
注意
在Docker上,您可以使用自定义启用日志init.sh
:
#!/bin/sh echo "log_statement = 'all'" >> /var/lib/postgresql/data/postgresql.conf
之后,您可以启动容器并使用来检查日志。docker logs
docker run -v "$(pwd)/init.sh":"/docker-entrypoint-initdb.d/init.sh" -d postgres docker logs -f <container-id>
PostgREST的数据库架构缓存经常使用户感到困惑。之所以存在,是因为检测表之间的外键关系(包括这些关系如何通过视图)是必需的,但代价很高。API请求将参考模式缓存作为资源嵌入的一部分。但是,如果架构在服务器运行时发生更改,则会导致缓存过时,并导致错误,指出在表之间未检测到任何关系。
要刷新缓存而不重新启动PostgREST服务器,请向服务器进程发送SIGUSR1信号:
killall -SIGUSR1 postgrest
以上是手动操作的方法。要自动重载架构,请使用如下数据库触发器:
CREATE OR REPLACE FUNCTION public.notify_ddl_postgrest() RETURNS event_trigger LANGUAGE plpgsql AS $$ BEGIN NOTIFY ddl_command_end; END; $$; CREATE EVENT TRIGGER ddl_postgrest ON ddl_command_end EXECUTE PROCEDURE public.notify_ddl_postgrest();
然后运行pg_listen实用程序以监视该事件,并在发生该事件时发送SIGUSR1:
pg_listen <db-uri> ddl_command_end $(which killall) -SIGUSR1 postgrest
现在,只要数据库模式的结构发生更改,PostgreSQL就会通知该ddl_command_end
通道,这将导致pg_listen
向PostgREST发送信号以重新加载其缓存。请注意,在上面的示例中,pg_listen需要可执行文件的完整路径。
对于使用systemd的 Linux发行版(ubuntu,debian,archlinux),您可以按以下方式创建守护程序。
首先,在以下位置创建postgrest配置 /etc/postgrest/config
db-uri = "postgres://<your_user>:<your_password>@localhost:5432/<your_db>" db-schema = "<your_exposed_schema>" db-anon-role = "<your_anon_role>" db-pool = 10 server-host = "127.0.0.1" server-port = 3000 jwt-secret = "<your_secret>"
然后在中创建systemd服务文件 /etc/systemd/system/postgrest.service
[Unit] Description=REST API for any Postgres database After=postgresql.service [Service] ExecStart=/bin/postgrest /etc/postgrest/config ExecReload=/bin/kill -SIGUSR1 $MAINPID [Install] WantedBy=multi-user.target
之后,您可以在启动时启用该服务,并使用以下命令启动它:
systemctl enable postgrest systemctl start postgrest ## For reloading the service ## systemctl restart postgrest
如Singular或Plural中所述,PostgREST中没有用于单个资源的特殊URL形式,仅用于过滤的运算符。因此,没有类似的网址/people/1
。它将被指定为
GET /people?id=eq.1 HTTP/1.1 Accept: application/vnd.pgrst.object+json
这允许使用复合主键,并使单数响应的意图独立于URL约定。
Nginx重写规则允许您模拟熟悉的URL约定。以下示例为所有表端点添加了重写规则,但您希望将其限制为具有名为“ id”的数字简单主键的表。
# support /endpoint/:id url style location ~ ^/([a-z_]+)/([0-9]+) { # make the response singular proxy_set_header Accept 'application/vnd.pgrst.object+json'; # assuming an upstream named "postgrest" proxy_pass http://postgrest/$1?id=eq.$2; }