<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: TOKUJI</title>
    <description>The latest articles on DEV Community by TOKUJI (@tokuji_30).</description>
    <link>https://dev.to/tokuji_30</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3988942%2F54c0c60b-e8a4-4908-9347-2a58d37564ba.png</url>
      <title>DEV Community: TOKUJI</title>
      <link>https://dev.to/tokuji_30</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tokuji_30"/>
    <language>en</language>
    <item>
      <title>Brewing Coffee Over HTTP: A Python Implementation of RFC 2324 and RFC 7168 (HTCPCP)</title>
      <dc:creator>TOKUJI</dc:creator>
      <pubDate>Thu, 18 Jun 2026 13:00:13 +0000</pubDate>
      <link>https://dev.to/tokuji_30/brewing-coffee-over-http-a-python-implementation-of-rfc-2324-and-rfc-7168-htcpcp-4hkc</link>
      <guid>https://dev.to/tokuji_30/brewing-coffee-over-http-a-python-implementation-of-rfc-2324-and-rfc-7168-htcpcp-4hkc</guid>
      <description>&lt;h2&gt;
  
  
  One terminal session. Copy, paste, watch it run.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;blackbull-htcpcp

&lt;span class="c"&gt;# ---- Coffee mode: start the server ----&lt;/span&gt;
python &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
from blackbull import BlackBull
from blackbull_htcpcp import HtcpcpExtension

app = BlackBull()
HtcpcpExtension(app=app, pot_type='coffee')
app.run(port=8000)
"&lt;/span&gt; &amp;amp;

&lt;span class="nb"&gt;sleep &lt;/span&gt;1  &lt;span class="c"&gt;# wait for the server to bind&lt;/span&gt;

&lt;span class="c"&gt;# ---- Brew coffee with additions ----&lt;/span&gt;
curl &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Accept-Additions: cream; sugar; vanilla'&lt;/span&gt; http://localhost:8000/pot

&lt;span class="c"&gt;# ---- Inspect the pot ----&lt;/span&gt;
curl &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt; http://localhost:8000/pot

&lt;span class="c"&gt;# ---- Ask when it'll be ready ----&lt;/span&gt;
curl &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt; http://localhost:8000/pot/when

&lt;span class="c"&gt;# ---- Teapot mode ----&lt;/span&gt;
&lt;span class="nb"&gt;kill&lt;/span&gt; %1 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;wait &lt;/span&gt;2&amp;gt;/dev/null
python &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"
from blackbull import BlackBull
from blackbull_htcpcp import HtcpcpExtension
app = BlackBull()
HtcpcpExtension(app=app, pot_type='teapot')
app.run(port=8000)
"&lt;/span&gt; &amp;amp;

&lt;span class="nb"&gt;sleep &lt;/span&gt;1  &lt;span class="c"&gt;# wait for the server to bind&lt;/span&gt;

&lt;span class="c"&gt;# Ask a teapot to brew coffee → 418&lt;/span&gt;
curl &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8000/pot

&lt;span class="c"&gt;# Ask a teapot to brew tea → 200&lt;/span&gt;
curl &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'\n'&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Accept-Additions: milk; honey'&lt;/span&gt; http://localhost:8000/pot

&lt;span class="nb"&gt;kill&lt;/span&gt; %1 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;wait &lt;/span&gt;2&amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see five JSON responses, each with &lt;code&gt;Content-Type: message/coffeepot&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;What you did&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Key detail&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;POST with &lt;code&gt;cream; sugar; vanilla&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;200&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Additions echoed back in the response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;GET the pot state&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;200&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"state":"ready"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;GET when it's ready&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;200&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;"ready":true&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Teapot + no additions&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;418&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;"I'm a teapot"&lt;/code&gt; — RFC 7168 §2.1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Teapot + &lt;code&gt;milk; honey&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;200&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Tea brewing succeeds — RFC 7168&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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

&lt;p&gt;&lt;a href="https://pypi.org/project/blackbull-htcpcp/" rel="noopener noreferrer"&gt;&lt;code&gt;blackbull-htcpcp&lt;/code&gt;&lt;/a&gt; is the first PyPI package to implement both &lt;a href="https://datatracker.ietf.org/doc/html/rfc2324" rel="noopener noreferrer"&gt;RFC 2324&lt;/a&gt; (the core HTCPCP spec) and &lt;a href="https://datatracker.ietf.org/doc/html/rfc7168" rel="noopener noreferrer"&gt;RFC 7168&lt;/a&gt; (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.&lt;/p&gt;




&lt;h2&gt;
  
  
  The real goal: stress-test an extension surface
&lt;/h2&gt;

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

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

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

&lt;p&gt;&lt;strong&gt;Spoiler: no core modifications were needed.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The extension surface
&lt;/h2&gt;

&lt;p&gt;BlackBull's extension model is deliberately minimal — no plugin registry, no dependency injection, no auto-discovery:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# app.route — accepts any RFC 9110 section 5.6.2 token since v0.42.1
&lt;/span&gt;&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/pot&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;BREW&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTPMethod&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;brew_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="c1"&gt;# app.on_error — custom handler for any HTTP status
&lt;/span&gt;&lt;span class="nd"&gt;@app.on_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HTTPStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IM_A_TEAPOT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;teapot_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="c1"&gt;# app.extensions — plain dict, stable key
&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;htcpcp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  What teapot discrimination looks like in code
&lt;/h3&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_pot_type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;teapot&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;has_coffee&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;COFFEE_ADDITIONS&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;additions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;has_tea&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;TEA_ADDITIONS&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;additions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;has_tea&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;has_coffee&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_teapot_response&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;  &lt;span class="c1"&gt;# 418
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="c1"&gt;# ...proceed to brew (200)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security: the threat model
&lt;/h2&gt;

&lt;p&gt;HTCPCP inherits HTTP's full attack surface. &lt;code&gt;Accept-Additions&lt;/code&gt; 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:&lt;/p&gt;

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

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




&lt;h2&gt;
  
  
  Prior art
&lt;/h2&gt;

&lt;p&gt;Roughly 105 HTCPCP repos on GitHub across all languages. The top implementations by stars:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Implementation&lt;/th&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Stars&lt;/th&gt;
&lt;th&gt;Last updated&lt;/th&gt;
&lt;th&gt;RFC 7168&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/HyperTextCoffeePot/HyperTextCoffeePot" rel="noopener noreferrer"&gt;HyperTextCoffeePot&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Python (Flask)&lt;/td&gt;
&lt;td&gt;78&lt;/td&gt;
&lt;td&gt;2015&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/madmaze/HTCPCP" rel="noopener noreferrer"&gt;madmaze/HTCPCP&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;49&lt;/td&gt;
&lt;td&gt;2011&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/stephen/node-htcpcp" rel="noopener noreferrer"&gt;node-htcpcp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Node.js&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;2013&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/dkundel/htcpcp-delonghi" rel="noopener noreferrer"&gt;htcpcp-delonghi&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;JS (Tessel 2)&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;2023&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;blackbull-htcpcp&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Python (BlackBull)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;—&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2026 (active)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

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




&lt;h2&gt;
  
  
  Why joke RFCs matter
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc1149" rel="noopener noreferrer"&gt;RFC 1149&lt;/a&gt; (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 &lt;a href="https://www.google.com/teapot" rel="noopener noreferrer"&gt;google.com/teapot&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The cultural through-line is that &lt;strong&gt;implementing joke protocols is a form of protocol literacy&lt;/strong&gt;. 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.&lt;/p&gt;




&lt;h2&gt;
  
  
  How this was tested
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Every claim above was tested against the published packages before writing.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test suite:&lt;/strong&gt; 53/53 passing (&lt;code&gt;pytest tests/ -v&lt;/code&gt; in &lt;code&gt;blackbull-htcpcp&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HTTP method validation:&lt;/strong&gt; &lt;code&gt;BREW&lt;/code&gt;, &lt;code&gt;PROPFIND&lt;/code&gt;, &lt;code&gt;WHEN&lt;/code&gt; all pass BlackBull's RFC 9110 §5.6.2 tchar check, added in v0.42.1 specifically to unblock this extension (&lt;a href="https://github.com/TOKUJI/BlackBull/blob/master/CHANGELOG.md#0421--2026-06-18" rel="noopener noreferrer"&gt;changelog&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Framework surface audit:&lt;/strong&gt; The extension imports only from &lt;code&gt;blackbull&lt;/code&gt; (public top-level): &lt;code&gt;BlackBull&lt;/code&gt;, &lt;code&gt;Response&lt;/code&gt;, &lt;code&gt;read_body&lt;/code&gt;, &lt;code&gt;Headers&lt;/code&gt;. No &lt;code&gt;blackbull._*&lt;/code&gt;, no &lt;code&gt;blackbull.server.*&lt;/code&gt;, no monkey-patches.&lt;/p&gt;

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




&lt;p&gt;&lt;em&gt;&lt;code&gt;blackbull-htcpcp&lt;/code&gt; is on &lt;a href="https://pypi.org/project/blackbull-htcpcp/" rel="noopener noreferrer"&gt;PyPI&lt;/a&gt; and &lt;a href="https://github.com/TOKUJI/blackbull-htcpcp" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. &lt;a href="https://github.com/TOKUJI/BlackBull" rel="noopener noreferrer"&gt;BlackBull&lt;/a&gt; is a pure-Python ASGI server with HTTP/1.1, HTTP/2, and WebSocket at the protocol level.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>http</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
