コンテンツにスキップ

POST ボディのストリーミング

wired_srvrun_handler.on_body は、SDK がリクエストボディを丸ごと(固定のウィンドウ 1 つ分まで) バッファーしてからハンドラーを呼ぶのではなく、届いたボディを順にストリーミングで受け取ります。 このページは、ボディのバイト数を積算してレスポンスとして返しつつ、 自前で 4096 バイトの上限を課します。 上限を超えたボディはその場で受信を打ち切り、413 を自動的に返します。 SDK 自身のウィンドウあふれのときに返るのと同じステータスです。

コード

main.c
#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;
}

実行結果

Terminal window
$ ./h3-post-body --port 4433
total 2048
conn closed by peer (CONNECTION_CLOSE)
conn closed by peer (CONNECTION_CLOSE)
Terminal window
$ probe h3req 127.0.0.1:4433 POST /upload --body-file small.txt
status 200
content-type: text/plain
2048
Terminal window
$ probe h3req 127.0.0.1:4433 POST /upload --body-file big.txt
status 413
  • wired_srvrun_handler.on_body は、ボディを運ぶ DATA フレームの 1 チャンクごとに順番に呼ばれ、 最後の呼び出しだけ fin=1 になります。req のメソッド、パス、ヘッダーは すでにデコードされていますが、ボディはこのコールバック経由で受け取るため req->body 自体は空のままです。
  • 0 を返すとボディの受信はその場で止まります。そのストリームに対する以降の呼び出しはなく、 レスポンスは 413 になります(その場合 wired_http_handler は呼ばれません)。 このページは、積算値が APP_BODY_LIMIT(4096 バイト)を超えた時点で 0 を返します。
  • チャンクの境目は、クライアントがボディをどう QUIC パケットに分割して送るかで決まるため、 実行ごとに固定ではありません。ページの出力が実行のたびに変わらないよう、 ログに出すのはボディ全体が揃った時点の合計だけです。
  • on_body と一緒に設定した wired_http_handler は、ボディが打ち切られずに完結した場合にだけ呼ばれ、 積算した合計をそのまま返します。