Streaming a POST body
wired_srvrun_handler.on_body streams a request body in as it arrives,
instead of having the SDK buffer it whole (up to one fixed window) before
the responder runs. This page accumulates the body’s byte count and answers
with it, enforcing a 4096-byte app limit of its own: a body over that limit
stops early and gets 413 automatically, the same status the SDK’s own
window overflow would answer.
Code
#define WIRED_MAIN#include "wired.h"
/* App's own request-body cap (RFC 9114 4.1), well under both demo files: * anything over this stops the body early and answers 413, same as the * SDK's own window overflow would without on_body registered. */#define APP_BODY_LIMIT 4096
static u64 g_total; /* bytes accumulated for the body in progress */static int g_active; /* 1 while a body is being accumulated */
/* Streams the request body in (wired_srvloop_on_body's sequencing): one * call per DATA chunk, in order, fin=1 on the last. Chunk boundaries follow * the client's own QUIC send packetization, which is not fixed run to run * -- only the running total is logged, once the body is whole. Returning 0 * past APP_BODY_LIMIT stops the body and answers 413 -- the handler below * is never called for that request. */static int on_body( void* ctx, const wired_h3reqdrive_req* req, u64 stream_id, wired_span chunk, int fin) { (void)ctx; (void)req; (void)stream_id; if (!g_active) { g_total = 0; g_active = 1; } g_total += chunk.n; if (g_total > APP_BODY_LIMIT) { g_active = 0; return 0; } if (fin) { wired_dprintf(2, "total %lu\n", g_total); g_active = 0; } return 1;}
/* Answers with the body's byte count once on_body has reassembled it * whole (g_total holds that count; on_body already logged it). */static int on_http(void* ctx, wired_http_exchange* x) { (void)ctx; x->content_type = "text/plain"; wired_obuf_printf(x->body, "%lu\n", g_total); return 1;}
int wired_main(int argc, char** argv) { /* The same fixed demo identity as the HTTP/3 hello page. */ static wired_srvboot_demo_keys keys; wired_srvboot_id id; wired_srvboot_demo(&id, &keys, 0x50, "guide-h3");
u16 port = (u16)wired_cliargs_int(argc, argv, "--port", 4433); wired_srvrun_handler h = {.http = on_http, .on_body = on_body}; wired_srvrun_obs obs = {0}; return wired_server_run(port, &id, h, obs) ? 0 : 1;}Output
$ ./h3-post-body --port 4433total 2048conn closed by peer (CONNECTION_CLOSE)conn closed by peer (CONNECTION_CLOSE)$ probe h3req 127.0.0.1:4433 POST /upload --body-file small.txtstatus 200content-type: text/plain
2048$ probe h3req 127.0.0.1:4433 POST /upload --body-file big.txtstatus 413How it works
Section titled “How it works”wired_srvrun_handler.on_bodyis called once per DATA chunk, in order, withfin=1on the last —reqis already decoded (method, path, headers) but its ownreq->bodystays empty, since the body is going through this callback instead.- Returning
0stops the body right there: no further call for the stream, and the response is413(wired_http_handleris never called for that request). This page returns 0 once its own running total passesAPP_BODY_LIMIT(4096 bytes). - Chunk boundaries follow the client’s own QUIC send packetization, which is not fixed from one run to the next — only the final total (once the body is whole) is logged, to keep the page’s output deterministic.
- The
wired_http_handlerset alongsideon_bodyonly runs once the body finished without being stopped; it answers with the accumulated total.