Exercises every route against a running server using the bundled client, so the request/response path can be checked without the HTTP bridge. Covers /hello with and without a name, percent- and plus-encoded query values, text and JSON POST bodies, and the 4.00/4.04/4.05 error paths. /health is matched by shape rather than an exact string since uptime moves. Exits non-zero on failure; COAP_HOST/COAP_PORT retarget it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CoAP Hello (UDP) — Rust MVP
A minimal CoAP server over UDP in Rust, built on coap-lite
(the RFC 7252 binary codec) and a tokio UdpSocket.
CoAP is "HTTP semantics on a UDP datagram" — the same methods, paths and query
strings, but each exchange is one compact binary datagram. Response codes are
written class.detail: 2.05 Content, 2.01 Created, 4.00 Bad Request,
4.04 Not Found, 4.05 Method Not Allowed.
coap-lite does message encoding only — the socket, the read loop and the
routing are all in src/main.rs, so nothing about the UDP layer is hidden.
Run
cargo run --release
Listens on udp://0.0.0.0:5683 (CoAP's IANA-assigned port).
Routes
| Method | Path | Response |
|---|---|---|
| GET | /hello |
2.05 Hello, World! |
| GET | /hello?name=Imran |
2.05 Hello, Imran! |
| POST | /hello |
2.01 Hello, <payload>! |
| GET | /health |
2.05 {"status":"ok","uptime":12} |
POST /hello takes either a raw text payload (Imran) or JSON
({"name":"Imran"}). An empty payload returns 4.00. An unknown path returns
4.04; a known path with the wrong method returns 4.05.
Check it in a browser
Browsers speak HTTP over TCP; this server speaks CoAP over UDP, so coap://
URLs cannot be opened directly. src/bin/bridge.rs is a small HTTP -> CoAP
proxy that closes the gap. Run both:
cargo run --release --bin coap-hello-udp
cargo run --release --bin bridge
Then open http://localhost:8080/ for a test console with a name field and a
button per route. Every response shows both the CoAP code and the HTTP status
it was mapped to; the raw CoAP code is also returned in an X-Coap-Code header.
These are now ordinary URLs — clickable, curl-able, bookmarkable:
| URL | Response |
|---|---|
http://localhost:8080/ |
the test console |
http://localhost:8080/hello |
Hello, World! |
http://localhost:8080/hello?name=Imran |
Hello, Imran! |
http://localhost:8080/health |
{"status":"ok","uptime":12} |
POST needs a body, so it is easiest from the console page or curl:
curl -X POST -d "Imran" http://localhost:8080/hello
The bridge is a development convenience: a deliberately small HTTP/1.1 subset
with no keep-alive, no chunked bodies and no TLS. Do not expose it. Set
HTTP_PORT if 8080 is taken.
Try it from the CLI
With the bundled client:
cargo run --bin client -- get "/hello?name=Imran"
cargo run --bin client -- post /hello "Imran"
Or with any other CoAP client, e.g. coap-cli:
coap get "coap://localhost:5683/hello?name=Imran"
Test
cargo test
Five unit tests cover the greeting, payload parsing (text + JSON) and query decoding.
Layout
| Path | What it is |
|---|---|
src/main.rs |
The server: socket, read loop, route table, helpers. |
src/lib.rs |
Shared CoAP client used by the CLI and the bridge. |
src/bin/client.rs |
CLI client for smoke tests. |
src/bin/bridge.rs |
HTTP -> CoAP proxy so a browser can reach the server. |
static/index.html |
The browser test console the bridge serves at /. |
nodejs/ |
The earlier Node.js version, kept for reference. |
Config
| Env var | Default | Used by |
|---|---|---|
COAP_PORT |
5683 |
server, client, bridge |
COAP_HOST |
0.0.0.0 |
server, client, bridge |
HTTP_PORT |
8080 |
bridge |
Design notes
- One task, no per-request spawn. Handling a datagram is pure CPU work with
no
.awaitinside, so it runs inline on the receive loop — no task-spawn overhead, and replies keep their arrival order. Spawn per datagram once a handler does real I/O (a database, an upstream call). - A fixed 1152-byte receive buffer, reused across requests, so the steady state allocates only for the response body. 1152 is the RFC 7252 guidance for staying inside an unfragmented IPv6 datagram.
- Hand-rolled JSON field read.
json_string_fieldhandles a flat{"name":"..."}and nothing more; it exists to avoid aserde_jsondependency for one field. Swap it out the moment bodies get real structure. - Lenient percent-decoding. RFC 7252 §6.4 says the client decodes before
splitting a URI into options, so a strict client sends
name=Md Imranalready decoded. Not all clients do, so the server decodes again — meaning a literal%in a name would be misread. Droppercent_decodeif your clients are known-conformant.
Next steps
- DTLS. Plaintext CoAP has no encryption or authentication. Production uses
coaps://on port 5684 — seewebrtc-dtlsor a gateway likelibcoap. - Block-wise transfer (RFC 7959) for payloads past ~1 KB;
coap-liteships aBlockHandlerfor this. - Observe (RFC 7641) for server-pushed updates;
coap-liteprovidesSubjectandcreate_notification. - Deduplication. A CoAP server should track recent message IDs and replay the cached response, since UDP clients retransmit. Fine to skip for idempotent greetings, needed once a POST has side effects.