Custom HTTP/1.1 server implemented from scratch in Python using raw TCP sockets.
- Raw TCP socket server (
AF_INET,SOCK_STREAM) - HTTPS/TLS listener with secure defaults (TLS 1.2+)
- Optional HTTP -> HTTPS redirect listener (
308 Permanent Redirect) - Dual runtime engines:
threadpoolengine (bounded worker pool)selectorsengine (non-blocking event loop)
- Persistent HTTP/1.1 keep-alive connections
- Pipelined request handling in selectors engine with ordered responses
- Chunked request body decoding (
Transfer-Encoding: chunked) - Incremental chunked response streaming (no pre-buffered stream payload)
- Incremental static-file delivery (
os.sendfilewhen available, read-chunk fallback) - Protocol hardening:
- HTTP/1.1 Host header enforcement
- HTTP version checks (
505for unsupported versions) - request-target length checks (
414) - header-size checks (
431) - unknown method handling (
501)
Expect: 100-continuesupport- Auto protocol headers:
Date,Server,Connection,Keep-Alive - Static cache validators (
ETag,Last-Modified,304 Not Modified) - Graceful shutdown + connection draining with
Retry-Afterrejection for post-drain requests - Built-in metrics endpoint:
GET /_metrics - Built-in API Playground at
GET /playground(mini-Postman style request lab) - Dynamic mock API with persistence:
POST /api/mocks,PUT /api/mocks/{id},DELETE /api/mocks/{id},GET /api/mocksGET /api/history,POST /api/replay/{request_id},GET /api/playground/state
- Scenario Runner with assertions, delay, and deterministic chaos:
- scenario CRUD:
GET/POST /api/scenarios,GET/PUT/DELETE /api/scenarios/{id} - execution:
POST /api/scenarios/{id}/run - run history:
GET /api/scenarios/{id}/runs,GET /api/scenarios/runs/{run_id} - CLI:
tools/scenario_runner.py run,list,run-remote,live,proxy
- scenario CRUD:
- Live Ops event pipeline shared by CLI + web:
GET /api/events/snapshotGET /api/events/stream
- Named target proxying (Postman-like external routing via your server):
GET/POST /api/targets,PUT/DELETE /api/targets/{id}POST /api/proxy/request
- Optional demo token guard for admin/live/proxy APIs
- Role-based auth sessions (
viewer,operator,admin) with local credential seeding - Live-event self-healing mode (
SSE -> snapshot fallback) with readiness endpoint - Event-driven playground Control Tower (live requests, scenario timeline, proxy activity)
- JSON-file state persistence (
data/server_state.json) for mock routes and request history - SQLite-backed persistent trend storage for p50/p95/p99 and error rates
- OpenAPI contract endpoint (
GET /openapi.json) with runnable examples - Route-level latency summaries (
p50/p95/p99) and error-class counters - Structured access logs with engine, connection id, request id, trace id, shutdown phase, playground actions
- Load-testing and benchmark tooling (
tools/loadgen.py,tools/compare_engines.py)
+----------------------+
TCP accept ----> | Engine Selector | ----> threadpool engine
| (threadpool/selectors)|
+----------+-----------+
|
+---------> selectors event loop
| read buffer
| parse/framing
| dispatch/router
| queued ordered writes
+--> socket send (body/stream/file)
| Behavior | Implemented |
|---|---|
| HTTP/1.1 + HTTP/1.0 parsing | Yes |
Missing Host on HTTP/1.1 |
400 Bad Request |
| Unsupported version | 505 HTTP Version Not Supported |
| Unknown method token | 501 Not Implemented |
| Known but disallowed method | 405 Method Not Allowed |
| Oversized request target | 414 URI Too Long |
| Oversized headers | 431 Request Header Fields Too Large |
| Oversized body | 413 Payload Too Large |
| Keep-alive semantics | Yes |
| Chunked request body decode | Yes |
| Chunked response stream encode | Yes |
Expect: 100-continue |
Yes |
| HTTPS/TLS server mode | Yes |
| HTTP -> HTTPS redirect | Yes |
GET /-> Hello world text responseGET /stream-> chunked demo responsePOST /submit-> echoes submitted bodyGET /echo/{str}-> echo endpoint (compatibility mode)GET /user-agent-> echoesUser-Agentheader (compatibility mode)GET /files/{filename}-> serves file bytes from--directory(compatibility mode)POST /files/{filename}-> writes request body to file in--directory(compatibility mode)GET /playground-> API Playground UIGET /playground-minimal-> Minimal Playground UI (Inter + Instrument Serif)GET /openapi.json-> OpenAPI contractGET /static/*-> serve static filesGET /_metrics-> JSON metrics snapshotGET /api/metrics/trends-> persistent trend points forwindow+routePOST /api/auth/login-> create role-backed session tokenGET /api/auth/me-> current role/session principalPOST /api/auth/logout-> revoke current session tokenGET /api/mocks-> list mock routesPOST /api/mocks-> create mock routePUT /api/mocks/{id}-> update mock routeDELETE /api/mocks/{id}-> delete mock routeGET /api/history-> list captured request historyPOST /api/replay/{request_id}-> replay a captured requestGET /api/playground/state-> current playground state snapshotGET /api/scenarios-> list scenariosPOST /api/scenarios-> create scenarioGET /api/scenarios/{id}-> fetch scenarioPUT /api/scenarios/{id}-> update scenarioDELETE /api/scenarios/{id}-> delete scenarioPOST /api/scenarios/{id}/run-> execute scenarioGET /api/scenarios/{id}/runs-> list runs for scenarioGET /api/scenarios/runs/{run_id}-> fetch one run reportGET /api/events/snapshot-> pull event batches since cursorGET /api/events/stream-> SSE event streamGET /api/events/live-mode-> live readiness and fallback guidanceGET /api/targets-> list proxy targetsPOST /api/targets-> create proxy targetPUT /api/targets/{id}-> update targetDELETE /api/targets/{id}-> delete targetPOST /api/proxy/request-> route request to a named target through this serverHEADsupported for routed/static/metrics endpoints
server.py: engine orchestration, lifecycle, dispatch, loggingthread_pool.py: fixed worker pool and bounded queuesocket_handler.py: framing/extraction utilities and incremental response writerrequest.py:HTTPRequestmodel and strict parserresponse.py:HTTPResponsemodel and serializer/preparermetrics.py: in-memory metrics counters and latency bucketsmetrics_store.py: persistent trend storage backends (memory,sqlite)auth_store.py: local user/role credential verification (PBKDF2)session_store.py: in-memory bearer session lifecycle + TTLrouter.py: route registry + lookuphandlers/example_handlers.py: route handlersutils.py: static path + MIME helpersconfig.py: runtime constants and tuning knobstools/loadgen.py: async load generator (keep-alive + pipeline options)tools/compare_engines.py: benchmark and gate checkertests/: unit + integration + phase validation testsopenapi/openapi.json: API contract for external testers
- Python 3.11+
- Dev dependencies from
requirements-dev.txt
python3 -m pip install -r requirements-dev.txt
tools/generate_dev_cert.sh certsFastest way (recommended):
scripts/start_server.shThis starts TLS mode (https://127.0.0.1:8443) and auto-generates local certs if missing.
Plain HTTP mode:
scripts/start_server.sh httpPreview command without starting:
scripts/start_server.sh --dry-runDefault (threadpool engine):
python3 server.pySelectors engine with JSON logs:
python3 server.py --engine selectors --log-format jsonTLS mode with redirect:
python3 server.py --enable-tls --cert-file certs/dev-cert.pem --key-file certs/dev-key.pem --https-port 8443 --redirect-httpUse compatibility mode to run with the CodeCrafters-style base behavior (/echo/*, /user-agent, /files/*):
./your_program.shWith a files directory for /files/{filename} routes:
./your_program.sh --directory /tmpYou can also enable it directly:
python3 server.py --codecrafters-mode --directory /tmpNotes:
- In compatibility mode, default port is
4221unless--portis explicitly provided. /echo/{str}supportsAccept-Encoding: gzipand returnsContent-Encoding: gzipwhen accepted.- Existing advanced routes/features remain available outside compatibility mode.
Server CLI options:
--host(default127.0.0.1)--port(default8080, or4221with--codecrafters-mode)--codecrafters-mode--directory(used by/files/{filename}in compatibility mode)--engine(threadpoolorselectors)--max-connections--keepalive-timeout--enable-tls,--cert-file,--key-file,--https-port--redirect-http/--no-redirect-http--drain-timeout--log-format(plainorjson)--enable-playground/--no-enable-playground--state-file(defaultdata/server_state.json)--history-limit(default500)--max-mock-body-bytes(default262144)--enable-scenarios/--no-enable-scenarios--scenario-state-file(defaultdata/scenarios_state.json)--max-scenarios(default100)--max-steps-per-scenario(default50)--max-assertions-per-step(default20)--default-chaos-seed(default1337)--enable-live-events/--no-enable-live-events--event-buffer-size(default5000)--event-heartbeat-secs(default5)--live-events-require-selectors/--no-live-events-require-selectors--cli-refresh-ms(default100)--enable-target-proxy/--no-enable-target-proxy--target-state-file(defaultdata/targets_state.json)--max-targets(default50)--demo-token,--require-demo-token/--no-require-demo-token--public-base-url--metrics-backend(memoryorsqlite)--metrics-sqlite-file(defaultdata/metrics.sqlite3)--metrics-retention-days(default30)--metrics-flush-interval-secs(default10)--auth-users-file(defaultdata/auth_users.json)--session-ttl-minutes(default480)
Role-based auth activates when either:
--require-demo-token/DEMO_TOKENis set, or--auth-users-fileexists with at least one user.
Seed file example:
cp data/auth_users.example.json data/auth_users.jsonRole policy:
viewer: read-only (events,history,state,trends, scenario reads)operator: viewer + incidents, replay, manual requests, scenario run, proxy requestadmin: operator + CRUD (mocks,scenarios,targets)
Defined in config.py:
SERVER_ENGINEWORKER_COUNT,REQUEST_QUEUE_SIZEMAX_ACTIVE_CONNECTIONSKEEPALIVE_TIMEOUT_SECS,MAX_KEEPALIVE_REQUESTSSELECT_TIMEOUT_SECS,IDLE_SWEEP_INTERVAL_SECSDRAIN_TIMEOUT_SECS,SHUTDOWN_POLL_INTERVAL_SECSREAD_CHUNK_SIZE,WRITE_CHUNK_SIZEMAX_HEADER_BYTES,MAX_BODY_BYTES,MAX_REQUEST_BYTES,MAX_TARGET_LENGTHENABLE_EXPECT_CONTINUEENABLE_TLS,TLS_CERT_FILE,TLS_KEY_FILE,HTTPS_PORT,REDIRECT_HTTP_TO_HTTPSENABLE_PLAYGROUND,STATE_FILE,HISTORY_LIMIT,MAX_MOCK_BODY_BYTES,MAX_MOCK_ROUTESENABLE_SCENARIOS,SCENARIO_STATE_FILE,MAX_SCENARIOSMAX_STEPS_PER_SCENARIO,MAX_ASSERTIONS_PER_STEP,DEFAULT_CHAOS_SEEDENABLE_LIVE_EVENTS,EVENT_BUFFER_SIZE,EVENT_HEARTBEAT_SECS,CLI_REFRESH_MS_DEFAULTENABLE_TARGET_PROXY,TARGET_STATE_FILE,MAX_TARGETSDEMO_TOKEN,REQUIRE_DEMO_TOKEN,PUBLIC_BASE_URLMETRICS_BACKEND,METRICS_SQLITE_FILE,METRICS_RETENTION_DAYS,METRICS_FLUSH_INTERVAL_SECSAUTH_USERS_FILE,SESSION_TTL_MINUTESSERVER_NAME
curl -i http://127.0.0.1:8080/
curl -i http://127.0.0.1:8080/stream
curl -i -X POST http://127.0.0.1:8080/submit -d "name=test"
curl -i http://127.0.0.1:8080/static/test.html
curl -i http://127.0.0.1:8080/_metrics
curl -i -X HEAD http://127.0.0.1:8080/Expect/continue quick check:
printf 'POST /submit HTTP/1.1\r\nHost: localhost\r\nExpect: 100-continue\r\nContent-Length: 4\r\nConnection: close\r\n\r\n' | nc 127.0.0.1 8080TLS quick check:
curl -k -i https://127.0.0.1:8443/
curl -i http://127.0.0.1:8080/Playground quick check:
curl -k -i https://127.0.0.1:8443/playground
curl -k -i https://127.0.0.1:8443/api/playground/state
curl -k -i -X POST https://127.0.0.1:8443/api/mocks \
-H 'Content-Type: application/json' \
-d '{"method":"POST","path_pattern":"/api/users","status":201,"headers":{"X-Mock-Server":"custom"},"body":"{\"created\":true}","content_type":"application/json"}'
curl -k -i -X POST https://127.0.0.1:8443/api/users
curl -k -i https://127.0.0.1:8443/api/historyScenario Runner quick check:
python3 tools/scenario_runner.py run examples/scenarios/user_lifecycle.json
python3 tools/scenario_runner.py run examples/scenarios/user_lifecycle.json --seed 42
python3 tools/scenario_runner.py list --server https://127.0.0.1:8443
python3 tools/scenario_runner.py live --server https://127.0.0.1:8443
python3 tools/scenario_runner.py proxy --server https://127.0.0.1:8443 --target-id <id> --method GET --path /Live Ops quick check:
curl -k -i "https://127.0.0.1:8443/api/events/snapshot?since_id=0&limit=20"
curl -k -i https://127.0.0.1:8443/api/events/stream
curl -k -i https://127.0.0.1:8443/api/events/live-mode
curl -k -i "https://127.0.0.1:8443/api/metrics/trends?window=24h&route=__all__"
curl -k -i https://127.0.0.1:8443/openapi.jsonKeep-alive + pipelined load:
python3 tools/loadgen.py --host 127.0.0.1 --port 8080 --path / --concurrency 200 --duration 20 --keepalive --pipeline-depth 4Compare both engines and evaluate gates:
python3 tools/compare_engines.py --path / --concurrency 200 --duration 10 --keepalive --pipeline-depth 4Output includes:
- per-engine summary (
requests,error_rate,rps,p50,p95) - markdown table for demo screenshots
- gate checks:
- selectors error rate
<= 1% - selectors p95
<= 200ms - selectors RPS
>= 1.8xthreadpool
- selectors error rate
python3 -m ruff check .
python3 -m pytest -q- Keep-alive client hangs:
- ensure client sends full HTTP framing (
\r\n\r\n, correct content-length/chunks) - verify
Connection: closeif one-shot behavior is expected
- ensure client sends full HTTP framing (
100 Continuenot observed:- send headers first with
Expect: 100-continue - do not send body bytes before waiting for interim response
- send headers first with
- TLS startup fails:
- verify cert/key files exist and are readable
- regenerate local certs with
tools/generate_dev_cert.sh certs
- Frequent
503 Service Unavailableunder load:- increase
WORKER_COUNTandREQUEST_QUEUE_SIZEfor threadpool runs - lower concurrency or switch to
--engine selectors
- increase
- Unexpected
413/431/414:- compare request size and target length against limits in
config.py
- compare request size and target length against limits in
Use the helper script to run phase-specific checks, then commit with fixed Conventional Commit messages.
scripts/autocommit_phase.sh P00
scripts/autocommit_phase.sh P01
scripts/autocommit_phase.sh P10
scripts/autocommit_phase.sh V20
# ...
scripts/autocommit_phase.sh V39By default, the script pushes the active branch after a successful commit.
Set SKIP_PUSH=1 to skip push and keep local-only commits.
For final showcase steps (server start, load profile, dashboard flow), use:
docs/demo-runbook.md- Technical demo stepsdocs/demo-recruiter.md- 2-minute recruiter script with timing and shareable URLdocs/api-examples.md- auth/session, trends, incidents, and OpenAPI curls
For Cursor Cloud Agents with Computer Use:
docs/cloud-agent-setup.md- Onboard configuration for demo recording