A simple HTTP debug out micro service. Because there's not enough out there.
It listens on PORT and reflects whatever it receives — any method, any path — back as JSON:
{
"method": "POST",
"path": "/a/b",
"headers": { "Content-Type": ["application/json"] },
"parameters": { "x": ["1", "2"] },
"body": "{\"targets\":[\"24.10.22.118\"]}"
}parameters holds the query string only, so body is always the raw bytes exactly as sent.
Every request is answered with 200, whatever the method or path, except in two cases:
-
A body that cannot be read (a connection dropped mid-request, for example) answers
400with the sameapplication/jsoncontent type:{ "error": "unable to read request body" } -
The router cleans the path before the handler sees it.
/a/../bis echoed as/b, and a path with an empty segment such as/a//bis answered with a307redirect to/a/binstead of being echoed. Everything else — including trailing slashes, percent-encoding and unusual methods — reaches the handler untouched.
Read from the environment, or from a .env file in the working directory. Real environment
variables always take precedence over .env.
| Variable | Required | Default | Description |
|---|---|---|---|
PORT |
no | 8080 |
TCP port |
OTEL_EXPORTER_OTLP_ENDPOINT |
no | — | OTLP collector, as collector:4317 or http://collector:4317; omit to disable telemetry |
OTEL_EXPORTER_OTLP_INSECURE |
no | false |
Set true for plaintext gRPC (typical for a local collector) |
OTEL_SERVICE_NAME |
no | httpecho-service |
Service name on all telemetry signals |
OTEL_RESOURCE_ATTRIBUTES |
no | — | Comma-separated key=value pairs added to every signal (e.g. deployment.environment=production) |
OTEL_TRACES_SAMPLER |
no | parentbased_always_on |
Sampling strategy |
Traces, logs and metrics are exported over OTLP/gRPC to a collector, which forwards them on —
Jaeger for traces, Loki for logs, Prometheus for metrics. Without
OTEL_EXPORTER_OTLP_ENDPOINT nothing is exported and no exporters are created; logs still go
to stderr, as they do when export is enabled.
docker run --rm -p 8080:8080 \
-e OTEL_EXPORTER_OTLP_ENDPOINT=collector:4317 \
-e OTEL_EXPORTER_OTLP_INSECURE=true \
-e OTEL_SERVICE_NAME=httpecho \
httpecho:devThe endpoint may be a bare collector:4317 or the specification's URL form
http://collector:4317. With a URL the scheme selects transport security; with a bare address
OTEL_EXPORTER_OTLP_INSECURE=true does. Setting it to true always forces plaintext.
Signals are batched, not sent per request: metrics leave every 60 seconds
(OTEL_METRIC_EXPORT_INTERVAL), traces and logs on their batch delays
(OTEL_BSP_SCHEDULE_DELAY, OTEL_BLRP_SCHEDULE_DELAY), and everything pending is flushed on
shutdown. A service that has just started looks quiet in Prometheus for up to a minute; that is
the interval, not a broken pipeline.
An unreachable collector never delays startup and never affects a request. On shutdown the
final flush waits up to 10 seconds for the collector before giving up; the failure is logged
and the process still exits 0, since telemetry is optional.
go run ./cmd/httpechodocker build -t httpecho:dev .
docker run --rm -p 8080:8080 httpecho:devcurl -s "http://localhost:8080/validate" \
-H "X-Api-Key: <key>" \
-H "X-Context: profile" | jq
curl -s -X POST "http://localhost:8080/a/b?x=1&x=2" \
-H "Content-Type: application/json" \
-d '{"source":["169.1.1.34"]}' | jqgo vet ./...
go test -race ./...