Skip to content

Repository files navigation

HTTPCan

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.

Crates.io ghcr.io License: MIT

Quick Links: Quick Start · Installation · Configuration · Examples · OpenAPI & Homepage · API Reference · Production · Library · Development · License

✨ Features

  • 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 /llm endpoints — point any SDK base_url at 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 /sse and /ndjson endpoints 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-to see 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-headers for more
  • Cloud‑native observability: /healthz liveness probe, /tags instance identification, and Server-Timing/X-Httpcan-Version headers 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

Quick Start

No install needed — the full API is hosted at https://httpcan.org:

curl https://httpcan.org/get

Or 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

Installation

🐳 Docker

# 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-*"

📦 Cargo

# 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-*"

🛠️ From Source

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 8080

🧰 Configuration

CLI 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:container mapping matches your --port if 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.

Usage Examples

# 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"}'

Auth

# 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

Status & Redirects

# 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‑to

Unlike a naive 302, /redirect-to detects browser clients (via Accept: 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 200 HTML 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: noindex to avoid search‑engine indexing.

Compression & Formats

curl -H "Accept-Encoding: gzip" https://httpcan.org/gzip
curl https://httpcan.org/json
curl https://httpcan.org/xml

Streaming (SSE/NDJSON)

# 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

AI API Mock

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

The 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).

OAuth 2.0 Mock

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

Cookies & Inspection

curl https://httpcan.org/cookies
curl https://httpcan.org/headers
curl https://httpcan.org/ip

OpenAPI & Homepage

  • 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 the Accept header — no JavaScript required to read the content.

API Reference

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.

HTTPCan Enhancements

  • Echo endpoint: /echo reflects request body and headers (multi‑method)
  • Methods+: QUERY HTTP method (RFC 9430 — a safe, idempotent GET with a body) accepted on /anything, /anything/{anything}, and /echo, with a dedicated /query endpoint 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 > request Content-Type > default; supports custom bodies via query/body
  • Redirects+: POST /redirect-to supports application/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+: /healthz liveness probe; /tags exposes HTTPCAN_* env vars; every response carries Server-Timing and X-Httpcan-Version headers
  • Method echo+: /method echoes any method name; /head /options /trace /query are dedicated echo endpoints; GET endpoints also answer HEAD
  • Status headers+: ?header=Name:Value injects response headers on /status/{codes} (repeatable; e.g. Retry-After for rate‑limit testing)
  • Body encoding+: POST to /gzip, /deflate, /brotli, /zstd, or /base64 returns the request body in the matching encoding

🚀 Production Considerations

Before deploying HTTPCan on the public internet, review these hardening options:

  • Open‑redirect protection on /redirect‑to: Browser clients (detected via Accept: text/html) receive an interstitial warning page showing the destination URL instead of a silent 302. This blocks phishing abuse via …/redirect-to?url=https://evil.com on your trusted domain. Programmatic clients (curl, httpx, …) still get the standard 302 for API compatibility. Non‑http(s) URL schemes render as a disabled link.
  • No reflected XSS on /base64: Decoded content is always returned as text/plain; charset=utf-8, never text/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".
  • Resource limits: --max-bytes caps /bytes and /stream-bytes responses (default 100KB); over‑limit requests return 404 instead of silently truncating (httpbin #594).
  • Non‑root Docker: The official image runs as a dedicated unprivileged user (uid 10001).

🎨 Self-Hosting & Customization

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.

🦀 Library Usage

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.

Development

# Run checks
cargo fmt --all
cargo clippy --all -- -D warnings
cargo test

# Run locally
cargo run -- --port 8080

Contributions are welcome! Please open issues/PRs for discussion.

License

MIT — see LICENSE.

About

Rust superset of httpbin.org: 84 drop-in endpoints, mock OpenAI/Anthropic LLM APIs, and a full OAuth 2.0 server - for testing HTTP clients, proxies, and AI agents.

Topics

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages