Skip to content

Commit aea4a97

Browse files
committed
docs: add README and agent instructions
README covers plugin behavior, static/dynamic Traefik configuration, mapping options, project layout, and local development commands. AGENTS.md documents commands and architecture for AI coding agents; CLAUDE.md references it.
1 parent a5140e3 commit aea4a97

3 files changed

Lines changed: 214 additions & 0 deletions

File tree

AGENTS.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## What this is
6+
7+
A Traefik middleware plugin (`github.com/muench-dev/query-to-header`) that copies values from
8+
incoming HTTP request query parameters into HTTP request headers before the request reaches
9+
the next handler in the chain. Traefik plugins are not run as compiled binaries in production —
10+
they are interpreted by [Yaegi](https://github.com/traefik/yaegi) inside Traefik itself, which
11+
constrains the code to the Go standard library only (no third-party dependencies).
12+
13+
## Commands
14+
15+
```bash
16+
go test -v -cover ./... # run all tests with coverage
17+
go test -run TestName ./... -v # run a single test
18+
go build ./... # sanity-compile (plugin is never shipped as a binary)
19+
go vet ./...
20+
golangci-lint run # lint (config in .golangci.yml)
21+
just # default recipe: lists all available recipes
22+
```
23+
24+
There is no `main` package and nothing is ever executed as a standalone program — `go build`
25+
is only a compile-correctness check.
26+
27+
## Architecture
28+
29+
Everything lives in a single package, `traefik_query_to_header` (package name uses underscores
30+
because the module path `query-to-header` is not a valid Go identifier):
31+
32+
- **`query_to_header.go`** — the entire plugin:
33+
- `Config` / `Mapping` — JSON-tagged structs populated by Traefik from the dynamic
34+
configuration (`mappings: [{query, header, remove, overwrite, bearer}]`).
35+
- `CreateConfig()` — required by the Traefik plugin contract; returns the default config.
36+
- `New(ctx, next, config, name)` — required factory signature Traefik calls to construct the
37+
middleware. Validates that every mapping has both `query` and `header` set, then returns a
38+
`*QueryToHeader` wrapping `next`.
39+
- `(*QueryToHeader).ServeHTTP` — the request path: for each configured mapping, reads the
40+
query parameter, optionally prefixes the value with `Bearer ` (`Bearer`), sets the header
41+
(skipping if already present unless `Overwrite` is true), optionally strips the query
42+
parameter (`Remove`), rewrites `req.URL.RawQuery`, then calls `next.ServeHTTP`.
43+
- **`query_to_header_test.go`** — table-style tests built around `httptest.NewRequest` /
44+
`httptest.NewRecorder`, asserting on header/query state observed inside a stub `next` handler.
45+
- **`.traefik.yaml`** — the plugin manifest required by the Traefik Plugin Catalog
46+
(`displayName`, `type: middleware`, `import`, `summary`, `testData` used to validate the
47+
catalog example config matches `Config`'s JSON shape).
48+
49+
## Conventions to preserve
50+
51+
- Keep `New` and `CreateConfig` signatures exact — Traefik's plugin loader calls them by
52+
reflection-free convention; changing the signature breaks plugin loading.
53+
- No external dependencies in `go.mod` — Yaegi's plugin sandbox only reliably supports the
54+
standard library for this kind of plugin.
55+
- Mapping validation (non-empty `query`/`header`) happens in `New`, not in `ServeHTTP`, so
56+
invalid configs fail fast at middleware construction time.

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@AGENTS.md

README.md

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# Query To Header
2+
3+
A [Traefik](https://traefik.io) middleware plugin that copies values from incoming HTTP
4+
request query parameters into HTTP request headers before the request reaches the next
5+
handler in the chain.
6+
7+
It is useful when an upstream service expects authentication tokens, tenant IDs, or other
8+
metadata in headers, but clients (or legacy integrations) only have the ability to send them
9+
as query parameters.
10+
11+
## How it works
12+
13+
For every configured mapping, the plugin:
14+
15+
1. Reads the named query parameter from the incoming request.
16+
2. If the parameter is absent, does nothing for that mapping.
17+
3. If a header with the target name already exists, the value is **left untouched** unless
18+
`overwrite: true` is set.
19+
4. Sets the header to the **first** value of the query parameter (query parameters can be
20+
repeated; only `values[0]` is used).
21+
5. If `bearer: true`, the value is prefixed with `Bearer ` before being set — useful for turning
22+
a query parameter directly into an `Authorization` header.
23+
6. If `remove: true`, deletes the query parameter from the request URL so it is not forwarded
24+
upstream.
25+
7. Mappings are applied in the order they are declared; later mappings can see headers/query
26+
changes made by earlier ones.
27+
28+
The plugin never touches the response — it only rewrites the request before calling the next
29+
handler.
30+
31+
## Installation
32+
33+
Traefik plugins are not compiled into a binary you install separately — Traefik downloads the
34+
plugin source and runs it through its embedded [Yaegi](https://github.com/traefik/yaegi)
35+
interpreter. You only need to reference it in Traefik's static configuration.
36+
37+
### Static configuration
38+
39+
Pin a specific tagged version (recommended for production):
40+
41+
```yaml
42+
experimental:
43+
plugins:
44+
query-to-header:
45+
moduleName: github.com/muench-dev/query-to-header
46+
version: v0.1.0
47+
```
48+
49+
```toml
50+
# traefik.toml
51+
[experimental.plugins.query-to-header]
52+
moduleName = "github.com/muench-dev/query-to-header"
53+
version = "v0.1.0"
54+
```
55+
56+
```bash
57+
# CLI
58+
--experimental.plugins.query-to-header.modulename=github.com/muench-dev/query-to-header
59+
--experimental.plugins.query-to-header.version=v0.1.0
60+
```
61+
62+
### Local plugin (for development against an unpublished version)
63+
64+
Point Traefik at a local checkout instead of a tagged release by using
65+
`localPlugins` and mounting/copying this repository into Traefik's plugins
66+
directory (`./plugins-local/src/github.com/muench-dev/query-to-header` by default):
67+
68+
```yaml
69+
experimental:
70+
localPlugins:
71+
query-to-header:
72+
moduleName: github.com/muench-dev/query-to-header
73+
```
74+
75+
## Dynamic configuration
76+
77+
Once declared in the static configuration, configure it as a middleware and attach it to a
78+
router:
79+
80+
```yaml
81+
http:
82+
middlewares:
83+
my-query-to-header:
84+
plugin:
85+
query-to-header:
86+
mappings:
87+
- query: token
88+
header: X-Token
89+
remove: true
90+
overwrite: false
91+
92+
routers:
93+
my-router:
94+
rule: Host(`example.com`)
95+
service: my-service
96+
middlewares:
97+
- my-query-to-header
98+
```
99+
100+
Equivalent labels (Docker provider):
101+
102+
```yaml
103+
labels:
104+
- "traefik.http.middlewares.my-query-to-header.plugin.query-to-header.mappings[0].query=token"
105+
- "traefik.http.middlewares.my-query-to-header.plugin.query-to-header.mappings[0].header=X-Token"
106+
- "traefik.http.middlewares.my-query-to-header.plugin.query-to-header.mappings[0].remove=true"
107+
```
108+
109+
### Mapping options
110+
111+
| Option | Type | Description | Default |
112+
|-------------|--------|----------------------------------------------------------------------------------|---------|
113+
| `query` | string | Name of the query parameter to read. Required. | - |
114+
| `header` | string | Name of the HTTP header to set with the query parameter's value. Required. | - |
115+
| `remove` | bool | Remove the query parameter from the request URL after copying it. | `false` |
116+
| `overwrite` | bool | Replace an existing header with the same name instead of leaving it untouched. | `false` |
117+
| `bearer` | bool | Prefix the header value with `Bearer ` (e.g. for `Authorization` headers). | `false` |
118+
119+
Multiple mappings can be declared; an empty `query` or `header` on any mapping causes the
120+
middleware to fail to load (validated in `New`, at construction time, not per-request).
121+
122+
## Project layout
123+
124+
| File | Purpose |
125+
|--------------------------------|----------------------------------------------------------------------|
126+
| `query_to_header.go` | Plugin implementation: `Config`, `CreateConfig`, `New`, `ServeHTTP`. |
127+
| `query_to_header_test.go` | Unit tests using `httptest`. |
128+
| `.traefik.yaml` | Plugin manifest required by the Traefik Plugin Catalog. |
129+
| `go.mod` | No third-party dependencies — required for Yaegi compatibility. |
130+
| `.golangci.yml` | Lint configuration. |
131+
| `justfile` | `lint`, `test`, `vendor`, `clean` recipes (run with [`just`](https://just.systems)). |
132+
133+
## Local development
134+
135+
```bash
136+
go build ./... # compile-correctness check (plugin is never shipped as a binary)
137+
go vet ./...
138+
go test -v -cover ./... # run all tests
139+
go test -run TestName -v ./... # run a single test
140+
golangci-lint run # lint, config in .golangci.yml
141+
just # default recipe: lists all available recipes
142+
```
143+
144+
### Testing against a real Traefik instance
145+
146+
1. Clone this repository into `./plugins-local/src/github.com/muench-dev/query-to-header`
147+
relative to your Traefik working directory.
148+
2. Add the `localPlugins` static configuration shown above.
149+
3. Start Traefik and confirm the plugin loads without errors in the logs.
150+
4. Attach the middleware to a router and send a request with the configured query parameter
151+
to confirm the header is set on the upstream request.
152+
153+
## Constraints
154+
155+
Traefik plugins run inside Yaegi, which only reliably supports the Go standard library.
156+
Do not add third-party dependencies to `go.mod` — the plugin must remain dependency-free to
157+
load correctly in the Traefik Plugin Catalog and in Yaegi generally.

0 commit comments

Comments
 (0)