Generated nginx configuration¶
gqlc render writes nginx configuration that you include, never a whole nginx.conf. Every name
it creates starts with gqlgw_, so it can live next to an existing configuration.
flowchart TB
subgraph http ["http {}"]
direction TB
a["lua_shared_dict gqlgw_cache, gqlgw_metrics"]
b["init_by_lua_block → gqlgw.init(gqlgw.lua)<br/>(unless [nginx] init = false)"]
c["geo $gqlgw_dollar (only if a value contains $)<br/>log_format gqlgw (only with access_log)"]
d["upstream gqlgw_#lt;source#gt; × N"]
subgraph srv ["server (default mode) — or your server with --locations-only"]
e["location = /graphql"]
f["location /_src/#lt;source#gt;/ × N (internal)"]
g["include server.d/*.conf"]
end
subgraph adm ["admin server (metrics and/or purge enabled)"]
h["location = /gqlgw_metrics"]
i["location = /gqlgw_purge"]
j["location / → 404"]
end
end
Upstreams¶
upstream gqlgw_users {
zone gqlgw_users 64k; # shared state, needed by "resolve"
resolver 10.53.0.1 valid=10s; # [nginx] resolver, scoped to this upstream
server users.internal:8080 resolve; # re-resolved at runtime (DNS names only)
keepalive 64; # [sources.users] keepalive
keepalive_timeout 60s;
}
- One upstream per source, named
gqlgw_<source>. An IP address gets a plainserverline and no resolver. - The resolver is declared inside the upstream, so it does not conflict with a resolver of your own configuration.
The GraphQL location¶
location = /graphql {
set $gqlgw_path ""; # the upstream path, set by the Lua side per subrequest
set $gqlgw_ctype ""; # Content-Type of subrequests with a body
set $gql_rounds "-"; # filled by the log phase, used by log_format gqlgw
set $gql_subrequests "-";
set $gql_cache "-";
client_max_body_size 1m; # [gateway] max_body
client_body_buffer_size 1m; # keep request bodies in memory
resolver 10.53.0.1 valid=10s; # only when memcached is given by name
access_log "/dev/stdout" gqlgw; # only with [gateway] access_log
content_by_lua_block { require("gqlgw").serve() }
log_by_lua_block { require("gqlgw").log() }
}
The set lines declare the variables. The Lua side gives $gqlgw_path and $gqlgw_ctype a
value for each subrequest (the vars option of ngx.location.capture_multi), which the /_src/
locations then read.
A source location¶
location /_src/users/ {
internal;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host "users.internal:8080";
proxy_set_header Accept-Encoding "";
proxy_set_header Content-Type $gqlgw_ctype;
proxy_pass_request_headers off;
proxy_set_header Authorization $http_authorization;
proxy_set_header Accept "application/json";
more_set_headers "X-Gqlgw-Upstream-Time: $upstream_header_time";
proxy_connect_timeout 1s;
proxy_send_timeout 5s;
proxy_read_timeout 2s;
include "/etc/gqlgw/sources.d/users/*.conf";
proxy_pass "http://gqlgw_users/api/v1$gqlgw_path";
}
| Directive | Why |
|---|---|
internal |
only subrequests reach it; /_src/users/x from a client is a 404 |
proxy_http_version 1.1, Connection "" |
keepalive connections to the upstream |
Host |
the host of the source URL (or host_header); otherwise nginx would send gqlgw_users |
Accept-Encoding "" |
subrequests inherit the client headers: the gateway must get plain JSON |
Content-Type $gqlgw_ctype |
application/json when the fetch has a body, nothing otherwise |
proxy_pass_request_headers off + proxy_set_header |
the allowlist (forward_headers) |
proxy_set_header X "" |
the denylist (drop_headers) |
Accept "application/json" |
unless Accept is forwarded or set |
more_set_headers X-Gqlgw-Upstream-Time |
upstream time for gqlgw_upstream_duration_seconds (only with metrics) |
| timeouts | connect_timeout, send_timeout, read_timeout of the source |
proxy_ssl_* (https) |
proxy_ssl_verify on, SNI with proxy_ssl_name (the URL host or tls_name), proxy_ssl_trusted_certificate (ca); or proxy_ssl_verify off with tls_verify = false |
include sources.d/<source>/*.conf |
your additions; an empty or missing directory is fine |
proxy_pass …<prefix>$gqlgw_path |
the source URL prefix, then the rendered path |
Why $gqlgw_path¶
The path of a subrequest URI is decoded by nginx: %2F would become / and an argument could
change the path sent to the service. The gateway passes the rendered, already-encoded path in
$gqlgw_path, and proxy_pass sends it verbatim. On top of that, strict_path_args refuses /,
\, . and .. as path segment values.
Literal $ in values¶
nginx interprets $ in directive values. When a set_headers value contains one, the renderer
writes it as ${gqlgw_dollar}, backed by geo $gqlgw_dollar { default "$"; }:
X-Api-Key = "abc$def" becomes proxy_set_header X-Api-Key "abc${gqlgw_dollar}def";.
The admin server¶
server {
listen 8191; # [metrics] port
access_log off;
allow 10.0.0.0/8; # [metrics] allow, then "deny all"
deny all;
location = /gqlgw_metrics { content_by_lua_block { require("gqlgw").metrics() } }
location = /gqlgw_purge { content_by_lua_block { require("gqlgw").purge() } }
location / { return 404; }
}
It exists when metrics or purge are enabled. In --locations-only mode it stays in
gqlgw.http.conf.
Extension points¶
Directory (next to gqlgw.toml) |
Included in | For |
|---|---|---|
server.d/*.conf |
the gateway server {} (default mode) |
TLS certificates, extra locations, limit_req |
sources.d/<source>/*.conf |
location /_src/<source>/ |
client certificates, extra headers, buffers |
conf.d/*.conf (Docker image only) |
http {} |
map, limit_req_zone, log_format |
In the Docker image, the directories are /etc/gqlgw/server.d, /etc/gqlgw/sources.d and
/etc/gqlgw/conf.d.
No lua_package_path¶
The Lua modules are installed in OpenResty's site/lualib, which is already in its search path.
The generated files never set lua_package_path, so they do not override yours.