Non-blocking HTTP/1.1 server for CircuitPython, MicroPython, and CPython.
Project description
chumicro-http-server
A non-blocking HTTP/1.1 server with @route — serve requests while your LED keeps blinking.
Routing with @server.route (method dispatch, path parameters), bounded multi-connection, per-tick byte budgets, and a streaming request parser — all without blocking your main loop. Serves TLS on every supported board pair except CircuitPython on RP2040 (CYW43 substrate limitation; documented inline). Self-contained — no chumicro-requests dependency on the device.
Part of the ChuMicro family — small, focused Python libraries for microcontrollers and laptops. Browse all libraries.
Install
# CircuitPython (after `circup bundle-add ChuMicro/ChuMicro-Bundle`)
circup install chumicro-http-server
# MicroPython
mpremote mip install github:ChuMicro/ChuMicro-Bundle/chumicro_http_server
# CPython
pip install chumicro-http-server
For bundle setup, pre-compiled .mpy bundles, the experimental channel, and details on PyPI naming, see the chumicro INSTALL guide.
Quick example
from chumicro_http_server import HttpServer, build_response
from chumicro_sockets import listener
from chumicro_timing import ticks_ms
server = HttpServer(
transport_factory=lambda: listener(host="0.0.0.0", port=8080),
)
@server.route("/")
def index(request):
return build_response(200, html="<h1>Hello from a Pi Pico W</h1>")
@server.route("/sensor", methods=["POST"])
def sensor(request):
payload = request.json()
return build_response(201, json={"ok": True})
@server.route("/widgets/<id>")
def widget(request):
return build_response(200, json={"id": request.path_params["id"]})
while True:
if server.check(ticks_ms()):
server.handle(ticks_ms())
What's included
| Symbol | Purpose |
|---|---|
HttpServer |
Runner-shaped HTTP/1.1 server; check(now_ms) / handle(now_ms). |
Request |
Per-request value object: method, path, query, headers, body, json(), text(). |
Response |
Outbound response: status_code, reason, headers, body. |
build_response(status, *, body, json, text, html, headers) |
Convenience builder with sensible Content-Type defaults. |
streaming.build_streaming_response(status, *, source, content_length, headers) |
Opt-in chumicro_http_server.streaming submodule — serve a body larger than the heap from a fill-a-buffer source(buffer) -> int (Content-Length or chunked framing, fixed staging window). |
RequestParser |
Streaming request parser (request line + headers + Content-Length body). |
parse_query / split_target |
URL helpers. |
ServerError + subclasses |
Typed exception hierarchy, independent of chumicro_requests so the server can ship without the client library. |
Each request is served on a fresh accepted socket and Connection: close is added to every response — HTTP/1.1 keep-alive and connection pooling are not supported. Chunked request bodies are not supported either; use Content-Length.
Need to return a body bigger than the heap — a log dump, a file, a long export? Return a streaming response from the opt-in chumicro_http_server.streaming submodule and the server drains it from a fill-a-buffer source one small window at a time, choosing Content-Length or chunked framing for you:
from chumicro_http_server.streaming import build_streaming_response, SOURCE_EOF
@server.route("/log")
def log_dump(request):
def source(buffer):
n = read_next_block_into(buffer) # your storage read; 0 <= n <= len(buffer)
return n if n else SOURCE_EOF # -1 signals end of body
return build_streaming_response(200, source=source)
See the user guide for the source contract, framing rules, fairness, and staging-window sizing.
Where this fits
Depends on chumicro-sockets (TCP listener) and chumicro-timing (ticks). Pairs with chumicro-websockets for combined HTTP + WS deployments. Self-contained otherwise — the shared HTTP/1.1 primitives (case-insensitive header dict, charset parsing) are inlined locally, so a server-only board never ships chumicro-requests.
Platform support
Works on CPython, MicroPython, and CircuitPython. Pure Python — no native extensions.
TLS server (HTTPS)
chumicro-http-server itself is transport-agnostic — pass a TLS-wrapped
listener from
chumicro_sockets.ssl_context_with_cert_and_key_paths
into transport_factory and the same HttpServer runs HTTPS. Live
verification across the supported board matrix:
| Runtime + board | TLS server status | Notes |
|---|---|---|
| CircuitPython on ESP32-S2 (Lolin S2) | ✅ Works | Bench-tested ~5 KB context (RSA-2048); each connection adds tens of KB during handshake — leave headroom. |
| CircuitPython on rp2 (Pi Pico W / Pi Pico 2 W) | ❌ Refused (UnsupportedSSLConfigError) |
chumicro_sockets.listener(tls=True) raises up-front; the underlying CYW43 TLS path raises OSError(32) mid-handshake AND wedges the chip's station-mode state. Use ESP32-family or MicroPython on rp2. |
| MicroPython on ESP32-S2 | ✅ Works | Hardware-accelerated handshake; ~1 KB heap. |
| MicroPython on rp2 (Pi Pico W) | ✅ Works (RSA-2048 only) | DER-encoded key; ~25 KB handshake heap; ECC keys fail at context build. |
Why the CP-on-rp2 row? The CYW43 stack's TLS server path raises
OSError(32)mid-handshake and wedges the chip's station state until a USB power-cycle. No upstream fix is in flight; for HTTPS server work on rp2, use MicroPython.
The TLS handshake is synchronous inside wrap_socket(..., server_side=True):
the listener stalls until it completes — single-digit to tens of
milliseconds on the supported board class with a local TLS client, longer
on a slow uplink as TLS rounds-trip. Once the handshake completes, the
per-connection state machine resumes its runner-shaped,
LED-blink-friendly progression.
Examples
| Example | What it shows |
|---|---|
simple_server.py |
Single-board HTTP server with GET /, GET /api/uptime, POST /api/echo routes. Drive it with curl from your laptop. Cross-runtime (CP + MP) — runtime marker on the file gates hardware-only deploys. For a two-physical-board demo see the workspace template's two_board_handshake/ example. |
Wiring wifi credentials for examples and functional tests
The hardware-prefixed examples + real-network suites in functional_tests/test_real_*.py need wifi credentials. See docs/wiring-wifi-credentials.md for the workspace-based and raw single-file paths. The library itself never reads TOML — it takes a transport_factory and goes; config wiring is application-layer.
Contributing
Working on chumicro-http-server itself? Clone the mono-repo if you haven't already — the rest of the workflow assumes you're inside that workspace.
pip install -e .[test]
pytest tests/ # host-side tests
pytest functional_tests/ # on-device tests (needs a board registered in devices.yml)
Register a board before running functional tests: chumicro-workspace add-device <id> --address <port>.
Docs
📖 Stable docs · Experimental docs
Find this library
- PyPI: chumicro-http-server
- Bundle: ChuMicro-Bundle (CircuitPython & MicroPython)
- Experimental bundle: ChuMicro-Bundle-Experimental
- Source: libraries/http_server
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file chumicro_http_server_experimental-0.18.0.tar.gz.
File metadata
- Download URL: chumicro_http_server_experimental-0.18.0.tar.gz
- Upload date:
- Size: 73.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ef7782b18c4b8fa3a651ec5d9e6c084133204cd49e5e739a6f75f0970374a47c
|
|
| MD5 |
e53469692474e00f6c4ec84b6de12dd1
|
|
| BLAKE2b-256 |
7f57a2649b5eb22965c5b348c22395e26f6e7c0d39c082640c26b674f6965e33
|
File details
Details for the file chumicro_http_server_experimental-0.18.0-py3-none-any.whl.
File metadata
- Download URL: chumicro_http_server_experimental-0.18.0-py3-none-any.whl
- Upload date:
- Size: 38.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fe7417a4f5d76f833b4fd0f206cd9da8524e3fa0a8584a0f34e5adabe30664cc
|
|
| MD5 |
39fbfb322f286d2615f0c487f3d91513
|
|
| BLAKE2b-256 |
1c2c8c9dfd7583e6d283b9675170a852212eb17cafc95ef84956ae16c5fb6e03
|