|
| 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