Pingap Plugin
Built-in plugins for Pingap.
A plugin is a small, self-contained unit of request/response logic that runs at a well-defined point of the proxy lifecycle. Plugins are declared once and then attached to any number of locations, which keeps routing configuration free of cross-cutting concerns such as authentication, rate limiting, caching or header rewriting.
How plugins are configured
A plugin is declared under [plugins.<name>]. <name> is arbitrary and is what
locations refer to; category selects which implementation is instantiated.
[]
= "ping"
= "/ping"
[]
= "api"
= "/api"
= ["pingPath"]
The same plugin can be declared several times with different settings, and one
location can list several plugins. Plugins run in the order they are listed in
plugins.
A location never runs without a plugin it names:
- A name in
pluginsthat is neither declared under[plugins.*]nor one of the built-inpingap:*plugins is a configuration error:--test, startup and a reload all reject it. - A plugin whose configuration is invalid (an unknown
categoryincluded) stops the process at startup. With--admina fresh start stays up so the configuration can be repaired through the admin, and the locations naming that plugin answer500until it is. A process that is to replace a running one (--upgrade, the restart of--autorestart) always exits instead, and the running one keeps serving. - On a hot reload a plugin whose new configuration is invalid keeps running
with its previous one, and the error is logged and sent as a
reload_config_failnotification. A new plugin that fails to build has nothing to fall back to: its locations answer500.
A value of the wrong type is an invalid configuration, not a default:
max = "100" where an integer is expected, hide_credentials = "true" where a
boolean is, a number in a list of strings, a duration that does not parse. The
error names the key and what it takes. Up to 0.15.0 such a value was silently
read as 0, false or an empty list.
A plugin whose category this build was compiled without is not an error by itself, so one configuration can be shared between builds; it is logged as unavailable. What a location that names it gets depends on the plugin:
image_optim(theimageoptim/fullbuilds) does nothing where it is missing: the image is served as it is.geo_restriction(thegeofeature) is access control, so a location that names it is a configuration error on a build without it.
Equivalent HCL and KDL forms are supported — see pingap-config.
Lifecycle steps
Pingap maps the pingora callbacks in pingap-proxy/src/server.rs onto five
PluginStep values. A plugin instance runs at exactly one request step:
| Step | When it runs | Typical use |
|---|---|---|
early_request |
Right after the request line and headers are read, before routing | Negotiating compression / Accept-Encoding |
request |
After a location has been matched | Auth, rate limiting, caching, mocking, static files |
proxy_upstream |
After the location is resolved, before a backend is chosen | Checks that should not run for cache hits |
upstream_response |
When the upstream response header arrives | Rewriting upstream headers, body transformations |
response |
When the response header is about to be sent downstream | Adding response headers, body substitution |
Setting step to a value a plugin does not implement makes it a silent no-op.
Only directory, limit, request_id and stats actually read step; every
other plugin pins its own step (documented per plugin below).
Each request-step plugin returns one of three results:
Skipped— the plugin did not apply; processing continues.Continue— the plugin ran and possibly mutated the request; processing continues.Respond(response)— the plugin terminates the request and this response is sent.
Plugin index
Authentication and authorization
| Category | Purpose | Doc |
|---|---|---|
basic_auth |
HTTP Basic authentication | docs/basic_auth.md |
key_auth |
API key in a header or query parameter | docs/key_auth.md |
jwt |
JWT verification (HMAC, public key or remote JWKS) and token minting | docs/jwt.md |
combined_auth |
app id + timestamp + HMAC digest, optionally IP bound | docs/combined_auth.md |
hmac_auth |
Requests signed over method, path, query, headers and body (HTTP Signatures) | docs/hmac_auth.md |
forward_auth |
Delegate the decision to an external HTTP service | docs/forward_auth.md |
oidc |
Login at an OpenID Connect provider, with the session in an encrypted cookie | docs/oidc.md |
csrf |
Double-submit-cookie CSRF protection | docs/csrf.md |
Access control
| Category | Purpose | Doc |
|---|---|---|
ip_restriction |
Allow/deny by IP or CIDR | docs/ip_restriction.md |
referer_restriction |
Allow/deny by Referer host |
docs/referer_restriction.md |
ua_restriction |
Allow/deny by User-Agent regex |
docs/ua_restriction.md |
geo_restriction |
Allow/deny by GeoIP country, embedded data or a .mmdb file; country header for the upstream (feature geo) |
docs/geo_restriction.md |
uri_block |
Block requests by path or query pattern, or by method | docs/uri_block.md |
maintenance |
Answer with a maintenance notice, except for allowed addresses or a header | docs/maintenance.md |
Traffic control
| Category | Purpose | Doc |
|---|---|---|
limit |
Rate limiting and concurrency limiting | docs/limit.md |
traffic_splitting |
Send a share of traffic to a different upstream | docs/traffic_splitting.md |
mirror |
Send a copy of the requests to a second address, and drop its answers | docs/mirror.md |
cache |
HTTP caching with PURGE support |
docs/cache.md |
bandwidth_limit |
Send response bodies no faster than a rate | docs/bandwidth_limit.md |
Content
| Category | Purpose | Doc |
|---|---|---|
compression |
gzip / brotli / zstd response compression | docs/compression.md |
accept_encoding |
Normalise the client Accept-Encoding header |
docs/accept_encoding.md |
directory |
Serve static files, with range requests and autoindex | docs/directory.md |
sub_filter |
Literal / regex substitution in the response body | docs/sub_filter.md |
request_headers |
Add / set / remove / rename the request headers sent to the upstream | docs/request_headers.md |
response_headers |
Add / set / remove / rename response headers | docs/response_headers.md |
cors |
CORS preflight and response headers | docs/cors.md |
redirect |
HTTP↔HTTPS redirects and path prefixing | docs/redirect.md |
error_page |
A location's own pages, or JSON, for the errors the proxy answers | docs/error_page.md |
image_optim |
Re-encode PNG/JPEG to WebP/AVIF (feature imageoptim) |
docs/image_optim.md |
Operations
| Category | Purpose | Doc |
|---|---|---|
ping |
Liveness endpoint returning pong |
docs/ping.md |
health |
Readiness endpoint: whether the upstreams have enough healthy backends | docs/health.md |
mock |
Return a canned response, optionally delayed, for all or a share of the requests | docs/mock.md |
request_id |
Generate or propagate a request id | docs/request_id.md |
stats |
JSON process and request statistics | docs/stats.md |
admin |
Web admin UI and configuration API | docs/admin.md |
stats and admin live in the pingap binary (src/plugin/) rather than in
this crate, because they need access to the process and the configuration
manager. image_optim lives in pingap-imageoptim. All of them register into
the same factory and are configured exactly the same way.
Writing a plugin
- Implement
pingap_core::Plugin. Only the hooks you need have to be overridden — every method has a default no-op implementation. - Parse
&PluginConfinTryFrom, using theget_*_confhelpers insrc/lib.rs, and reject invalid combinations there so thatpingap -tcatches them before the proxy starts. A helper that finds a value of the wrong type answers the default and notes it; the factory then fails the plugin with that note, so the type of each key needs no check of your own. - Return
get_hash_key(conf)fromconfig_key(). Pingap uses it to detect whether a hot reload actually changed this plugin instance. - Register the category with the
register_plugin!macro, which expands to a pre-main constructor. - A plugin that only sets response headers, and should set them on a response
another plugin answered with as well (as
corsdoes for a401), returnstruefromhandles_plugin_response(). Itshandle_responseis then also called for those responses, which never reach the response step.
use async_trait;
use ;
use Session;
use Cow;
register_plugin!;
Features
geo— enables thegeo_restrictionplugin (embedded Tor GeoIP database, and the reader for MaxMind DB files).
License
Apache-2.0.