A modern, high‑performance superset of httpbin.org for testing HTTP clients, proxies, and AI agents — with mock OpenAI/Anthropic LLM APIs and a full OAuth 2.0 server. Built with Rust and Actix Web.
Hosted at https://httpcan.org — every endpoint is live there, zero install.
Quick Links: Quick Start · Installation · Configuration · Examples · OpenAPI & Homepage · API Reference · Production · Library · Development · License
- 84-endpoint superset of httpbin.org: every httpbin.org endpoint covered and drop‑in compatible, plus 33 endpoints httpbin.org doesn't have and 18 it has but httpcan fixes or extends — see the homepage for the full badge‑tagged list
- AI API mock: OpenAI- and Anthropic-compatible
/llmendpoints — point any SDKbase_urlat it (streaming included) and test AI clients without a paid provider - OAuth 2.0 mock: a full authorization server at
/oauth2— four RFC 6749 grants (authorization code + PKCE, implicit, password, rotating refresh, client credentials), Bearer-protected userinfo, and RFC 8414 discovery - AI‑friendly streaming: native
/sseand/ndjsonendpoints with OpenAI/Ollama‑compatible chunk formats - Measured performance: in our benchmarks against comparable HTTP testing servers, HTTPCan led on both throughput and tail latency
- Anti‑phishing redirects: browser clients hitting
/redirect-tosee a confirmation page instead of a silent 302, closing an open‑redirect abuse vector - Correct header handling: duplicate and non‑ASCII request headers are preserved instead of being dropped or crashing the server
- Safer by default: built‑in filtering strips ~100 reverse‑proxy/CDN headers from echoed responses, with
--exclude-headersfor more - Cloud‑native observability:
/healthzliveness probe,/tagsinstance identification, andServer-Timing/X-Httpcan-Versionheaders on every response - Self‑documenting homepage:
/lists every endpoint by category with compatibility badges and one‑click "Copy curl" buttons — fully static HTML, crawlable by search engines and AI agents - Tiny & fast: <10MB Docker image, minimal memory footprint, high throughput via Actix Web + Tokio
No install needed — the full API is hosted at https://httpcan.org:
curl https://httpcan.org/getOr run your own:
# Docker (recommended)
docker run -p 8080:8080 ghcr.io/seedvector/httpcan:latest
curl http://localhost:8080/get
# Cargo
cargo install httpcan
httpcan
curl http://localhost:8080/get# Latest image
docker run -p 8080:8080 ghcr.io/seedvector/httpcan:latest
# Custom port
docker run -p 3000:3000 ghcr.io/seedvector/httpcan:latest --port 3000
# Header filtering
docker run -p 8080:8080 ghcr.io/seedvector/httpcan:latest --exclude-headers "foo, x-bar-*"# Install globally
cargo install httpcan
# Run
httpcan
httpcan --port 3000
httpcan --exclude-headers "foo, x-bar-*"
httpcan --port 3000 --no-current-server --exclude-headers "x-forwarded-*,cf-*"git clone https://github.com/seedvector/httpcan.git
cd httpcan
# Default (8080)
cargo run
# Custom port
cargo run -- --port 3000
# Release build
cargo build --release
./target/release/httpcan --port 8080CLI flags:
| Option | Description | Default | Example |
|---|---|---|---|
-p, --port <PORT> |
Port number to listen on. Also settable via HTTPCAN_PORT |
8080 |
--port 3000 |
--openapi-servers <MODE> |
How /openapi.json builds its servers array: current-first (default) prepends the instance the visitor is talking to; spec-only serves the spec's own servers verbatim. Also settable via HTTPCAN_OPENAPI_SERVERS |
current-first |
--openapi-servers spec-only |
--exclude-headers <HEADERS> |
Exclude headers in responses; comma‑separated; supports wildcard suffix (e.g., x-bar-*). Also settable via HTTPCAN_EXCLUDE_HEADERS |
"" |
--exclude-headers "x-forwarded-*,cf-*,server" |
--max-bytes <BYTES> |
Max bytes for /bytes, /stream-bytes, /range, and /drip; over‑limit returns 404 instead of truncating (httpbin #594). Also settable via HTTPCAN_MAX_BYTES |
102400 |
--max-bytes 1048576 |
--canonical-scheme <auto|http|https> |
Scheme for SEO‑facing URLs only (canonical link, sitemap.xml, robots.txt); doesn't affect copy‑curl examples or the OpenAPI current server, which always mirror the actual request. Also settable via HTTPCAN_CANONICAL_SCHEME |
auto |
--canonical-scheme https |
--static-dir <DIR> |
Static assets directory: user overrides for openapi.json, favicon.png, index.html, robots.txt, sitemap.xml, plus extra files served at /static/<name> or /<name>. Also settable via HTTPCAN_STATIC_DIR |
binary-relative static, else ./static |
--static-dir /etc/httpcan/static |
--oauth2-clients <ID:SECRET,…> |
OAuth 2.0 client registry for /oauth2: when set, client secrets are validated (invalid_client on mismatch); when empty, any non‑empty secret passes (mock mode). Also settable via HTTPCAN_OAUTH2_CLIENTS |
(empty) | --oauth2-clients "webapp:s3cr3t,cli:an0ther" |
-h, --help |
Print help information | --help |
|
-V, --version |
Print version | --version |
Notes:
- Built‑in filtering includes reverse proxy/CDN providers (Nginx, Cloudflare, AWS, GCP, Azure).
- When using Docker, ensure
-p host:containermapping matches your--portif you override it.
Any flag backed by an environment variable (e.g. HTTPCAN_CANONICAL_SCHEME) can also be set via a .env file in the working directory — handy for a bare‑binary deployment on a server, where there's no Docker --env-file/env_file: or systemd EnvironmentFile= to inject real environment variables for you:
# .env
HTTPCAN_CANONICAL_SCHEME=https./httpcan # picks up .env from the current directory automatically.env values are loaded before CLI flags are parsed, so an explicit --canonical-scheme flag still takes precedence over HTTPCAN_CANONICAL_SCHEME in .env.
# Basic GET
curl https://httpcan.org/get
# POST with JSON
curl -X POST https://httpcan.org/post \
-H "Content-Type: application/json" \
-d '{"key":"value"}'# Basic auth
curl -u username:password https://httpcan.org/basic-auth/username/password
# Username only (empty password) — enhanced
curl -u username: https://httpcan.org/basic-auth/username# Specific status
curl https://httpcan.org/status/418
# Random from list
curl https://httpcan.org/status/200,404,500
# Inject response headers (rate-limit / Retry-After testing)
curl -H "Accept: application/json" "https://httpcan.org/status/429?header=Retry-After:120&header=X-RateLimit-Remaining:0"
# Redirect to a URL (supports form/json)
curl -X POST https://httpcan.org/redirect-to -d "url=https://example.com"🔒 Open‑redirect protection on
/redirect‑toUnlike a naive 302,
/redirect-todetects browser clients (viaAccept: text/html) and returns an interstitial warning page instead of silently redirecting. This prevents phishing abuse where attackers exploit your trusted domain (e.g.…/redirect-to?url=https://evil.com).
- Programmatic clients (curl, httpx, …) still receive the standard
302— no API breakage.- Browser clients get a
200HTML page showing the destination URL and requiring an explicit click.- Non‑
http(s)URL schemes (javascript:,data:, …) render as a disabled link.- Destination URLs are HTML‑escaped in the interstitial to prevent XSS.
- The page carries
X-Robots-Tag: noindexto avoid search‑engine indexing.
curl -H "Accept-Encoding: gzip" https://httpcan.org/gzip
curl https://httpcan.org/json
curl https://httpcan.org/xml# SSE
curl https://httpcan.org/sse?count=3&format=simple
curl https://httpcan.org/sse/5?format=openai&delay=2000
# NDJSON
curl https://httpcan.org/ndjson?count=3&format=simple
curl https://httpcan.org/ndjson/5?format=ollama&model=llama3&delay=1500# OpenAI-compatible chat completions (streaming supported)
curl -X POST https://httpcan.org/llm/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"gpt-5.6-sol","messages":[{"role":"user","content":"Hello"}]}'
# Anthropic-compatible messages
curl -X POST https://httpcan.org/llm/v1/messages \
-H "Content-Type: application/json" \
-d '{"model":"claude-sonnet-5","max_tokens":64,"messages":[{"role":"user","content":"Hello"}]}'
# OpenAI-compatible Responses API
curl -X POST https://httpcan.org/llm/v1/responses \
-H "Content-Type: application/json" \
-d '{"model":"gpt-5.6-sol","input":"Hello"}'
# OpenAI-compatible legacy completions (text-in/text-out)
curl -X POST https://httpcan.org/llm/v1/completions \
-H "Content-Type: application/json" \
-d '{"model":"gpt-5.6-sol","prompt":"Once upon a time"}'Point an SDK at it — no API key needed (any value is accepted), and responses are deterministic:
from openai import OpenAI
client = OpenAI(base_url="https://httpcan.org/llm/v1", api_key="not-needed")
reply = client.chat.completions.create(
model="gpt-5.6-sol",
messages=[{"role": "user", "content": "Hello"}],
extra_body={"httpcan": {"content": "Custom reply text"}},
)
print(reply.choices[0].message.content) # Custom reply textThe Anthropic SDK works with base_url="https://httpcan.org/llm" (it appends /v1/messages itself). GET /llm returns a self-describing index, and GET /llm/v1/models returns the model list in whichever family's shape the request looks like (an anthropic-version header switches it).
# 1) Send the resource owner to the authorization endpoint (consent page)
open "https://httpcan.org/oauth2/authorize?response_type=code&client_id=webapp&redirect_uri=https%3A%2F%2Fexample.com%2Fcb&state=xyz&scope=read"
# 2) Exchange the one-time code (Basic or form-body client auth; PKCE supported)
curl -X POST https://httpcan.org/oauth2/token \
-u webapp:s3cr3t \
-d "grant_type=authorization_code&code=<code>&redirect_uri=https://example.com/cb"
# 3) Call the Bearer-protected resource
curl https://httpcan.org/oauth2/userinfo -H "Authorization: Bearer <token>"All four RFC 6749 grants work: authorization_code (with PKCE S256/plain), password, refresh_token (rotating — a presented refresh token is single-use), and client_credentials. response_type=token (implicit flow, legacy) hands out the access token in the redirect fragment. Codes are one-time, tokens are HMAC-signed per process, and the consent email is the only identity. GET /oauth2 is a self-describing index; GET /.well-known/oauth-authorization-server serves RFC 8414 metadata for discovery-based clients.
curl https://httpcan.org/cookies
curl https://httpcan.org/headers
curl https://httpcan.org/ip- OpenAPI spec:
GET /openapi.json - API catalog (RFC 9727, automated discovery):
GET /.well-known/api-catalog— a Linkset pointing agents at the spec, docs, and health probe; URLs mirror the request origin - Homepage: visit
/for a static, crawlable page listing every endpoint by category, each with a compatibility badge (Enhanced/New) relative to httpbin.org and a one‑click "Copy curl" button (pre‑filled with sample parameters and resolved against the instance you're viewing). It always renders as HTML, regardless of theAcceptheader — no JavaScript required to read the content.
Endpoints are grouped into the same categories shown on the homepage (/) — visit a running instance for the full interactive list with compatibility badges and copy‑ready curl examples:
| Category | Endpoints |
|---|---|
| HTTP Methods | /get /post /put /patch /delete /method /head /options /trace /query /echo |
| Anything | /anything /anything/{path} |
| Auth | /basic-auth /hidden-basic-auth /bearer /jwt-bearer /digest-auth |
| Status codes | /status/{codes} |
| Request inspection | /headers /ip /user-agent |
| Response inspection | /cache /etag /response-headers |
| Response formats | /json /xml /html /robots.txt /deny /encoding/utf8 /encoding/iso-8859-1 /gzip /deflate /brotli /zstd |
| Dynamic data | /uuid /base64 /bytes /stream-bytes /stream /range /links /drip /delay |
| Cookies | /cookies /cookies/set /cookies/delete |
| Images | /image /image/png /image/jpeg /image/webp /image/svg |
| Redirects | /redirect /relative-redirect /absolute-redirect /redirect-to |
| Streaming | /sse /ndjson |
| AI API Mock | /llm/v1/chat/completions /llm/v1/messages /llm/v1/responses /llm/v1/completions /llm/v1/models |
| OAuth 2.0 Mock | /oauth2/authorize /oauth2/token /oauth2/userinfo |
| Observability | /healthz /tags |
Every endpoint carries a compatibility badge relative to httpbin.org:
- (no badge) — drop‑in compatible with httpbin.org
- Enhanced — httpbin.org has this endpoint, but httpcan fixes a bug or extends it
- New — not available in httpbin.org
For full parameter details and schemas, consult the OpenAPI spec.
- Echo endpoint:
/echoreflects request body and headers (multi‑method) - Methods+:
QUERYHTTP method (RFC 9430 — a safe, idempotent GET with a body) accepted on/anything,/anything/{anything}, and/echo, with a dedicated/queryendpoint echoing URL args plus the parsed body - Auth+: Basic auth with username only; JWT Bearer decode/inspect at
/jwt-bearer - Status+: Content‑type priority:
Accept> requestContent-Type> default; supports custom bodies via query/body - Redirects+:
POST /redirect-tosupportsapplication/x-www-form-urlencoded,multipart/form-data,application/json; browser clients get an open-redirect interstitial (see above) - Streaming+: SSE/NDJSON endpoints with
count,delay, and AI formats (OpenAI/Ollama) - File uploads+: Multiple files with the same field return as array across multipart endpoints
- Observability+:
/healthzliveness probe;/tagsexposesHTTPCAN_*env vars; every response carriesServer-TimingandX-Httpcan-Versionheaders - Method echo+:
/methodechoes any method name;/head/options/trace/queryare dedicated echo endpoints; GET endpoints also answerHEAD - Status headers+:
?header=Name:Valueinjects response headers on/status/{codes}(repeatable; e.g.Retry-Afterfor rate‑limit testing) - Body encoding+: POST to
/gzip,/deflate,/brotli,/zstd, or/base64returns the request body in the matching encoding
Before deploying HTTPCan on the public internet, review these hardening options:
- Open‑redirect protection on
/redirect‑to: Browser clients (detected viaAccept: text/html) receive an interstitial warning page showing the destination URL instead of a silent302. This blocks phishing abuse via…/redirect-to?url=https://evil.comon your trusted domain. Programmatic clients (curl, httpx, …) still get the standard302for API compatibility. Non‑http(s)URL schemes render as a disabled link. - No reflected XSS on
/base64: Decoded content is always returned astext/plain; charset=utf-8, nevertext/html. Browsers display the raw text without rendering embedded<script>tags. - Response header filtering:
- Built‑in (always on): ~100 reverse‑proxy/CDN headers are stripped from all echoed responses by default — Nginx (
x-real-ip,x-forwarded-*), Cloudflare (cf-*), AWS CloudFront/ALB (cloudfront-*,x-amzn-*), GCP (x-appengine-*,x-cloud-trace-context), Azure (x-azure-*,x-ms-*). Prevents infrastructure information leakage without any configuration. - Custom (
--exclude-headers): Add your own patterns to strip additional sensitive headers, with wildcard suffix support:--exclude-headers "x-internal-*,server,x-secret-token".
- Built‑in (always on): ~100 reverse‑proxy/CDN headers are stripped from all echoed responses by default — Nginx (
- Resource limits:
--max-bytescaps/bytesand/stream-bytesresponses (default 100KB); over‑limit requests return404instead of silently truncating (httpbin #594). - Non‑root Docker: The official image runs as a dedicated unprivileged user (
uid 10001).
The binary is fully self-contained: the OpenAPI spec and favicon are embedded at compile time, and /, /robots.txt, and /sitemap.xml are generated per request. To customize your instance, drop files into a static assets directory (--static-dir, or the default static/ next to the binary / ./static; in Docker, mount a volume at /httpcan/static — files must be world-readable for uid 10001):
| File in static dir | Replaces | Notes |
|---|---|---|
openapi.json |
embedded spec at /openapi.json |
The current server is still injected into servers (verbatim with --openapi-servers spec-only) |
favicon.png |
embedded favicon at /favicon.png |
|
index.html |
generated homepage at / |
Forfeits origin-resolved curl examples, markdown negotiation, and Link headers |
robots.txt |
generated policy at /robots.txt |
E.g. Disallow: * for private instances |
sitemap.xml |
generated index at /sitemap.xml |
Absolute URLs in a static file don't follow the request origin or --canonical-scheme |
Behavior notes:
- Files are checked per request — adding or replacing a file takes effect immediately, no restart. The directory itself must exist at startup.
- Every other file in the directory is served at
/static/<name>(and at/<name>when the name doesn't collide with an API route). - Reserved: files named after API routes (
json,get,image, …) never shadow those routes; dot‑prefixed files are rejected with 400.
Add dependency:
[dependencies]
httpcan = "0.6"Embed server:
use httpcan::HttpCanServer;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
HttpCanServer::new()
.port(3000)
.host("127.0.0.1")
.exclude_headers(vec!["foo".into(), "x-bar-*".into()])
.run()
.await?;
Ok(())
}More examples and advanced config: see LIBRARY_USAGE.md.
# Run checks
cargo fmt --all
cargo clippy --all -- -D warnings
cargo test
# Run locally
cargo run -- --port 8080Contributions are welcome! Please open issues/PRs for discussion.
MIT — see LICENSE.