feat: bind Doppel's own variables into every template - #12
Merged
Conversation
A template could see only what its own mock declared, so the things every mock
wants -- which proxy answered, which version answered, who called -- had to be
extracted by hand in each one, and two of them could not be extracted at all.
Nine system variables now, bound into every render: `proxy_name`, `mock_name`,
`doppel_version`, `request_id`, `method`, `path`, `host`, `peer_ip`, `real_ip`.
`real_ip` is the chain a proxy in front writes: `X-Real-IP`, then the *leftmost*
`X-Forwarded-For` entry -- the original client rather than the hop next to us,
across field lines, because a chain split over several is legal and some proxies
emit it -- then the connection's own address, so the variable always has a value
and no template needs `| default('')`. `peer_ip` is that address on its own, and
the pair is deliberate: one is a claim anybody can make and the other is a fact,
and a mock that decides something needs to know which it is reading.
They are reserved. Bound *after* the operator's extractions, so a mock that
extracts into `proxy_name` finds Doppel's value rather than its own -- the
alternative is a template whose meaning depends on which mock rendered it. The
extraction still costs a header read, so `startup_advisories` names the mock and
the names it shadowed. The list of names lives in `doppel-core`, which is the
crate both the binding and the validation can see.
`server.external_url` may now be a template over them, rendered per request:
`http://{{ host }}/` answers each client with the address it asked for, and
`https://{{ proxy_name }}.gw.example.com/` gives each proxy its own name behind a
wildcard. Opt-in for a reason -- `Host` is the caller's claim, so a deployment
that builds a redirect out of it is choosing to, and the documentation says so in
a warning rather than a footnote. A template that fails to render, or renders to
something that is not a url, means no rewriting for that request rather than a
failed one: a cosmetic improvement to a `Location` is not worth a 500.
Every variable name in `main.example.yaml` and the documentation is `snake_case`
now, matching the system ones. Jinja accepts either, and an operator's own names
are untouched -- but two conventions in one context read as two sources.
The reference configuration also stopped extracting `X-Request-ID` into
`request_id`: it is a system variable, always present and minted when the client
sends none, so the extraction was exactly the wasted work the new advisory warns
about. `mock4` keeps the strict-rendering lesson on `X-Trace-Id`, which is
genuinely the operator's, and renders `proxy_name` and `doppel_version` beside it.
Seventeen unit tests, three on the templated address, one end-to-end through the
binary over a real socket -- the `real_ip` chain needs a peer to fall back to, and
only a connection has one. Mutation-checked twice: nothing binding the system
variables fails the end-to-end test with `undefined variable proxy_name`, and
ignoring `X-Real-IP` fails the chain test.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Nine variables Doppel binds itself, a templated
external_url, andsnake_casethroughout the reference configuration.
What a template can see now
A template could see only what its own mock declared, so the things every mock
wants -- which proxy answered, which version, who called -- were extracted by hand
in each one, and two of them could not be extracted at all.
proxy_namemock_namedoppel_versionrequest_idX-Request-ID, minted when the client sent nonemethod,pathhostHostthe client asked forpeer_ipreal_ipreal_ipis the chain a proxy in front writes:X-Real-IP, then the leftmostX-Forwarded-Forentry -- the original client rather than the hop next to us, andacross field lines, because a chain split over several is legal and some proxies
emit it -- then the connection's own address. So it always has a value and no
template needs
| default('').peer_ipbeside it is deliberate: one is a claim anybody can make and the otheris a fact, and a mock that decides something needs to know which it is reading.
Reserved, and why the advisory exists
They are bound after the operator's extractions, so a mock that extracts into
proxy_namefinds Doppel's value rather than its own -- the alternative is atemplate whose meaning depends on which mock rendered it. The extraction still
costs a header read, so startup names the mock and what it shadowed:
The list of names lives in
doppel-core: the binding is indoppel-renderandthe validation is in core, and core is the crate both can see.
A templated external_url
A value containing
{{is a template rendered per request; anything else isparsed as a url when the configuration is read, as before. The scheme has to be
literal, because a value that does not start with one cannot become a usable url
however it renders.
{{ host }}is opt-in for a reason, and the documentation says so in a warning:Hostarrives from the client, so a deployment that builds a redirect out of itis choosing to let a caller decide where its own redirects point. A template that
fails to render, or renders to something that is not a url, means no rewriting for
that request rather than a failed one -- improving a
Locationis not worth a 500.snake_case, and one extraction removed
Every variable name in
main.example.yamland the documentation issnake_case,matching the system ones. An operator's own names are untouched; Jinja accepts
either.
The reference configuration also stopped extracting
X-Request-IDintorequest_id, which is now exactly the wasted work the advisory warns about.mock4keeps the strict-rendering lesson onX-Trace-Id, which is genuinely theoperator's, and renders
proxy_nameanddoppel_versionbeside it.Verification
make gateclean,make e2eclean (81 tests). Seventeen unit tests, three on thetemplated address, and one end-to-end through the built binary over a real socket
-- the
real_ipchain needs a peer to fall back to, and only a connection has one.Mutation-checked twice: with nothing binding the system variables the end-to-end
test fails with
undefined variable proxy_name, and ignoringX-Real-IPfails thechain test.