simple-http-dump-server
v1.0.2
Published
Tiny HTTP server that answers every request with 200 and dumps it to stdout and, optionally, a JSON Lines file. A fake upstream for webhooks and client testing.
Maintainers
Readme
simple-http-dump-server
A tiny HTTP server that answers every request with an empty
200 OKand shows you exactly what was sent.
Point a webhook, a client under test or a misbehaving integration at it and see the method, path, headers and body of every request. Think of it as a self-hosted RequestBin in a single file with two small dependencies.
$ npx simple-http-dump-server
Listening on port 8000 ...
2026-10-07T08:30:32.274Z [::1] - 200 4.072 ms
POST /webhooks/github HTTP/1.1
{"host":"localhost:8000","user-agent":"GitHub-Hookshot/abc123","authorization":"<redacted>","content-type":"application/json","content-length":"21"}
{"action": "opened"}✨ Features
- Accepts anything. Any method, any path, any content type. Bodies up to 50 MB by default.
- Readable output. One block per request: summary line, request line, headers, then the body (truncated to 4 KB, so the log aggregator doesn't fill up).
- Machine-readable recordings. JSON Lines with full bodies and JSON already parsed, ready for
jq. - Dump over HTTP.
GET /__dumpreturns the recordings andDELETE /__dumpclears them, which suits test suites. - Records rejected requests too. A body over the limit gets
413and still shows up in the log and the dump. - Doesn't leak credentials.
Authorization,Proxy-Authorization,CookieandX-Api-Keyare always printed as<redacted>. - Easy to run.
npx, a multi-arch Docker image (amd64/arm64), or a public endpoint on Azure Container Instances. - Container-friendly. Runs as numeric UID 1000 (works with
runAsNonRoot), fails fast on a bad config, and shuts down cleanly ondocker stop.
🚀 Quick start
npx (Node.js 22+):
npx simple-http-dump-serverDocker:
docker run --rm -p 8000:8000 ghcr.io/denis1stomin/simple-http-dump-serverThe old Docker Hub image
denis1stomin/simple-http-dump-serveris outdated (2018). Use theghcr.ioimage above. The Docker Hub copy is updated only if the CI has Docker Hub credentials.
Then send it something:
curl -X POST http://localhost:8000/some/path \
-H 'content-type: application/json' \
-d '{"SomeProp": "value"}'⚙️ Configuration
All settings are environment variables.
| Variable | Default | Description |
|---------------------|-----------|-----------------------------------------------------------------------------|
| PORT | 8000 | Port to listen on. |
| BODY_LIMIT | 50mb | Max request body size (100kb, 10mb, ...). Larger bodies get a 413. |
| LOG_BODY_LIMIT | 4kb | Max body bytes printed to stdout; the rest is cut with … (truncated, N bytes total). 0 hides bodies. |
| DUMP_FILE | (unset) | Store recordings in this file (appended, full bodies). Without it they are kept in memory. |
| DUMP_BUFFER_LIMIT | 64mb | Size of the in-memory buffer when DUMP_FILE isn't set; the oldest records are evicted first. |
| DUMP_API_PATH | /__dump | Path of the dump API. No authentication; set it to an empty string to turn the API off. |
If DUMP_FILE can't be opened (missing directory, read-only filesystem) or a value is invalid, the server exits at startup with a one-line error instead of failing on the first request.
PORT=9000 BODY_LIMIT=200mb DUMP_FILE=run.jsonl npx simple-http-dump-server📄 Recordings (JSON Lines)
Every request is recorded as one JSON object per line, in memory or in DUMP_FILE:
{"ts":"2026-10-07T08:30:32.270Z","method":"POST","url":"/some/path","status":200,"headers":{"content-type":"application/json","content-length":"21"},"body":"{\"SomeProp\": \"value\"}","json":{"SomeProp":"value"}}| Field | Description |
|-----------------|----------------------------------------------------------------------------------------------|
| ts | When the request was answered (ISO 8601). |
| method, url | Request method and URL (with query string). |
| status | Status the server answered with: 200, or 413 for bodies over BODY_LIMIT. |
| headers | Only content-type and content-length, so no credentials end up in the recordings. |
| body | Full body as UTF-8 text (empty for rejected requests). |
| json | Parsed body, present only when the content type is JSON (application/json, *+json) and the body parses. |
A record is stored before the response is sent. Once a client has its response, the record is already available.
🔌 Reading the dump over HTTP
The server itself serves the recordings, so tests can fetch them without kubectl cp or exec rights on the pod:
| Request | Response |
|------------------|-------------------------------------------------------------------------------------|
| GET /__dump | 200, all recordings as application/x-ndjson. In memory mode, the X-Dump-Dropped header says how many old records were evicted. |
| DELETE /__dump | 204, clears the recordings (truncates DUMP_FILE, or empties the buffer). |
| other methods | 405 |
Calls to the dump API are themselves neither recorded nor logged. If the client under test needs the /__dump path itself, change DUMP_API_PATH.
🔓 The dump API has no authentication. Anyone who can reach the server can read every recorded body (credential headers are never stored, but bodies are stored in full) and can clear the dump. That's fine on localhost or inside a cluster where only your tests can reach the service. Anywhere else, turn the API off with
DUMP_API_PATH=(empty), or at least restrict access to the port, e.g. with a KubernetesNetworkPolicy.
curl -s http://localhost:8000/__dump | jq -c 'select(.status != 200) | {ts, url, status}'
curl -s -X DELETE http://localhost:8000/__dumpExample pytest fixture:
import json
import pytest
import requests
DUMP = "http://http-dump:8000/__dump"
@pytest.fixture
def recorded():
requests.delete(DUMP).raise_for_status() # start every test from a clean dump
def fetch():
resp = requests.get(DUMP)
resp.raise_for_status()
return [json.loads(line) for line in resp.text.splitlines()]
return fetch
def test_prices_are_uploaded(recorded):
run_sync() # the code under test calls the fake upstream
uploads = [r for r in recorded() if r["url"] == "/erp/sync/prices"]
assert uploads and all(r["status"] == 200 for r in uploads)
assert len(uploads[0]["json"]) == 5000More jq examples:
# URLs of all POST requests
jq -r 'select(.method == "POST") | .url' run.jsonl
# number of items in each JSON array body
jq '.json | length' run.jsonlTo get the dump file on the host when running in Docker, mount a directory. The container runs as UID 1000, so the directory must be writable by it:
docker run --rm -p 8000:8000 \
-e DUMP_FILE=/dump/run.jsonl -v "$PWD:/dump" \
ghcr.io/denis1stomin/simple-http-dump-server☸️ Kubernetes
The image runs fine with a hardened security context. Recordings are kept in memory by default, so a read-only root filesystem needs no extra volume:
containers:
- name: http-dump
image: ghcr.io/denis1stomin/simple-http-dump-server:latest
ports: [{ containerPort: 8000 }]
securityContext:
runAsNonRoot: true
readOnlyRootFilesystem: true
allowPrivilegeEscalation: falseTests then use GET and DELETE http://<service>:8000/__dump. The API has no authentication, so keep the Service cluster-internal (ClusterIP, no Ingress). If the pod has to be reachable from outside, turn the API off with - { name: DUMP_API_PATH, value: "" }. To keep recordings across many runs or beyond DUMP_BUFFER_LIMIT, add an emptyDir at /tmp and set DUMP_FILE=/tmp/run.jsonl.
☁️ Public endpoint with Azure Container Instances
Need a URL that a cloud service can reach? Run the container on ACI:
az group create --name http-dump --location centralus
CONTAINER_DNS_NAME=$(uuidgen | tr '[:upper:]' '[:lower:]')
az container create --name http-dump-server --resource-group http-dump \
--image ghcr.io/denis1stomin/simple-http-dump-server \
--ip-address public --ports 8000 --dns-name-label "$CONTAINER_DNS_NAME"
az container attach --name http-dump-server --resource-group http-dump # follow the output
curl "http://$CONTAINER_DNS_NAME.centralus.azurecontainer.io:8000/some/path"When you're done, delete everything with az group delete --name http-dump.
⚠️ A public endpoint accepts requests from anyone, and
GET /__dumpshows anyone what was recorded. Don't leave it running longer than you need it, and turn the dump API off: add--environment-variables DUMP_API_PATH=toaz container create. A hard-to-guess path likeDUMP_API_PATH=/$(uuidgen)only hides the API; it isn't authentication.
🛠️ Development
git clone https://github.com/denis1stomin/simple-http-dump-server.git
cd simple-http-dump-server
npm install
npm run lint # ESLint
npm test # node:test
npm startCI/CD
GitHub Actions (ci.yml):
| Trigger | What happens |
|---------------------|--------------------------------------------------------------------------------------------------|
| Push / pull request | ESLint and tests on Node 22 and 24, hadolint, multi-arch image build, container smoke test |
| Push to master | Image pushed to ghcr.io/denis1stomin/simple-http-dump-server:master |
| Push (not PRs) | Also pushed to Docker Hub when DOCKERHUB_USERNAME / DOCKERHUB_TOKEN secrets are set |
| Tag vX.Y.Z | Image tagged X.Y.Z, X.Y, latest; package published to npm with provenance |
To release, bump version in package.json, then push a matching vX.Y.Z tag. CI rejects the release if the tag and version don't match.
📜 License
MIT © Denis Istomin
