Brewing Coffee Over HTTP: A Python Implementation of RFC 2324 and RFC 7168 (HTCPCP)

python dev.to

One terminal session. Copy, paste, watch it run.

pip install blackbull-htcpcp

# ---- Coffee mode: start the server ----
python -c "
from blackbull import BlackBull
from blackbull_htcpcp import HtcpcpExtension

app = BlackBull()
HtcpcpExtension(app=app, pot_type='coffee')
app.run(port=8000)
" &

sleep 1  # wait for the server to bind

# ---- Brew coffee with additions ----
curl -w '\n' -X POST -H 'Accept-Additions: cream; sugar; vanilla' http://localhost:8000/pot

# ---- Inspect the pot ----
curl -w '\n' http://localhost:8000/pot

# ---- Ask when it'll be ready ----
curl -w '\n' http://localhost:8000/pot/when

# ---- Teapot mode ----
kill %1 && wait 2>/dev/null
python -c "
from blackbull import BlackBull
from blackbull_htcpcp import HtcpcpExtension
app = BlackBull()
HtcpcpExtension(app=app, pot_type='teapot')
app.run(port=8000)
" &

sleep 1  # wait for the server to bind

# Ask a teapot to brew coffee → 418
curl -w '\n' -X POST http://localhost:8000/pot

# Ask a teapot to brew tea → 200
curl -w '\n' -X POST -H 'Accept-Additions: milk; honey' http://localhost:8000/pot

kill %1 && wait 2>/dev/null
Enter fullscreen mode Exit fullscreen mode

You'll see five JSON responses, each with Content-Type: message/coffeepot:

# What you did Status Key detail
1 POST with cream; sugar; vanilla 200 Additions echoed back in the response
2 GET the pot state 200 "state":"ready"
3 GET when it's ready 200 "ready":true
4 Teapot + no additions 418 "I'm a teapot" — RFC 7168 §2.1
5 Teapot + milk; honey 200 Tea brewing succeeds — RFC 7168

Native method support: BREW, PROPFIND, and WHEN are registered as first-class HTTP methods when the underlying BlackBull framework is ≥ 0.42.1. Before BlackBull 0.42.1, POST served as the fallback for BREW and GET covered PROPFIND / WHEN — both paths produce identical wire behaviour.

blackbull-htcpcp is the first PyPI package to implement both RFC 2324 (the core HTCPCP spec) and RFC 7168 (its tea-pot extension). 53 tests. A documented threat model. And every feature is wired through the framework's public extension API — zero core modifications.


The real goal: stress-test an extension surface

HTCPCP is a joke, but it is a joke with excellent test coverage of HTTP's extension points. A protocol that exercises all five of these in 350 lines is a useful validation tool:

Extension point HTCPCP usage
Custom HTTP method BREW, PROPFIND, WHEN — non-IANA verbs as first-class routes
Custom status code 418 I'm a teapot with application-level semantics
Custom content type message/coffeepot on every /pot response
Application-defined header Accept-Additions: cream; sugar; whisky with its own grammar
Error semantics 418 is not "server error" or "client error" — it means "wrong pot type"

If you can ship all five purely on app.route(), app.on_error(), and app.extensions, the extension surface is real. If you need to modify the framework core, it is not.

Spoiler: no core modifications were needed.


The extension surface

BlackBull's extension model is deliberately minimal — no plugin registry, no dependency injection, no auto-discovery:

# app.route — accepts any RFC 9110 section 5.6.2 token since v0.42.1
@app.route(path='/pot', methods=['BREW', HTTPMethod.POST])
async def brew_handler(scope, receive, send): ...

# app.on_error — custom handler for any HTTP status
@app.on_error(HTTPStatus.IM_A_TEAPOT)
async def teapot_handler(scope, receive, send): ...

# app.extensions — plain dict, stable key
app.extensions['htcpcp'] = self
Enter fullscreen mode Exit fullscreen mode

That is the entire API surface the extension touches. Four @app.route decorators, one app.on_error(418), and one dict assignment. The remaining 300 lines are the Accept-Additions parser, the pot state machine (idle -> brewing -> ready), and the teapot discrimination logic from RFC 7168 section 2.1.

What teapot discrimination looks like in code

RFC 7168 adds nuance: a teapot does not always return 418. Asked to brew tea (with explicit tea additions like milk, lemon, honey), it returns 200. Asked to brew coffee, or asked with no additions at all, it returns 418:

if self._pot_type == 'teapot':
    has_coffee = any(a in COFFEE_ADDITIONS for a in additions)
    has_tea    = any(a in TEA_ADDITIONS for a in additions)
    if not has_tea or has_coffee:
        await send(self._teapot_response())  # 418
        return
# ...proceed to brew (200)
Enter fullscreen mode Exit fullscreen mode

This is the kind of detail that separates "I made a 418 joke endpoint" from "I implemented the RFC." Prior Python implementations (HyperTextCoffeePot, 78 stars, Flask, archived 2015) handled 418 but not RFC 7168 teapot/coffee-pot discrimination.


Security: the threat model

HTCPCP inherits HTTP's full attack surface. Accept-Additions is a semicolon-delimited header parsed from untrusted input — it needs the same hardening as any other HTTP header parser. The test suite documents 15 security cases (S001–S015); the most illustrative are shown below:

Case Category Threat Mitigation
S001–S003 Header injection CRLF / LF / NULL byte in addition tokens Rejected at parse -> 400
S004–S005 DoS Token > 256 chars, or > 64 tokens Rejected -> 400
S006–S007 DoS Concurrent BREW, body > 1 MiB 409 / 413
S008 Control chars Non-printable chars below 0x20 (except HTAB) Rejected -> 400
S010 418 abuse Cache poisoning via 418 + Cache-Control No permissive cache headers on 418
S014 Smuggling BREW + Content-Length / Transfer-Encoding games Body capped, not interpreted
S015 Replay Race: two BREW requests simultaneously State machine guards brewing state

Every rejection — 400, 409, 413 — returns Content-Type: message/coffeepot. The error format is consistent regardless of which security boundary was hit. The protocol is absurd; the parser is not.


Prior art

Roughly 105 HTCPCP repos on GitHub across all languages. The top implementations by stars:

Implementation Language Stars Last updated RFC 7168
HyperTextCoffeePot Python (Flask) 78 2015 No
madmaze/HTCPCP C 49 2011 No
node-htcpcp Node.js 36 2013 No
htcpcp-delonghi JS (Tessel 2) 29 2023 No
blackbull-htcpcp Python (BlackBull) 2026 (active) Yes

blackbull-htcpcp is the first Python package on PyPI implementing HTCPCP, one of two known implementations covering both RFCs, the only ASGI-native one, and actively maintained with 53 automated tests.


Why joke RFCs matter

RFC 1149 (IP over Avian Carriers, 1990) was implemented by the Bergen Linux User Group in 2001 — 9 packets over 5 km, 55% packet loss, mostly pigeon-related. The #save418 movement in 2017 kept status 418 in Python, Go, and Node.js. Google serves google.com/teapot.

The cultural through-line is that implementing joke protocols is a form of protocol literacy. HTCPCP is small enough to implement in an afternoon, but it touches custom methods, custom status codes, custom content types, header parsing, and error semantics — the same extension points a real application protocol would need.


How this was tested

Every claim above was tested against the published packages before writing.

Test suite: 53/53 passing (pytest tests/ -v in blackbull-htcpcp).

HTTP method validation: BREW, PROPFIND, WHEN all pass BlackBull's RFC 9110 §5.6.2 tchar check, added in v0.42.1 specifically to unblock this extension (changelog).

Framework surface audit: The extension imports only from blackbull (public top-level): BlackBull, Response, read_body, Headers. No blackbull._*, no blackbull.server.*, no monkey-patches.

End-to-end wire output: All responses shown in the terminal session at the top of this article were verified against the published packages using blackbull.testing.TestClient; the curl commands shown produce identical output.


blackbull-htcpcp is on PyPI and GitHub. BlackBull is a pure-Python ASGI server with HTTP/1.1, HTTP/2, and WebSocket at the protocol level.

Source: dev.to

arrow_back Back to Tutorials