feat(http): add HttpServer runtime stats and onAccept/onClose hooks - #881
Conversation
Add connection-level hooks and runtime counters to HttpServer, which the existing per-request handler chain cannot express. - onAccept(hio_t*)/onClose(hio_t*) hooks on http_server_t. onAccept runs before any HTTP parsing (before the TLS handshake on https) and returning false rejects the connection, enabling ip allow/deny and per-connection accounting; onClose pairs with it for connection lifecycle. - HttpServerStat embedded in http_server_t: cur_connections, total_connections, total_requests, total_recv_bytes, total_send_bytes (atomic, per-process). Exposed via HttpServer::getStat(). - connectionNum() now reads stat.cur_connections (atomic) instead of locking and summing per-loop counts. - send bytes are counted by owning the io write_cb (on_send); the writer Channel is still located via hio_context and its onwrite dispatched, so streaming (sendfile/SSE) keeps working. recv bytes counted in on_recv. - examples/http_server_test.cpp: /stats endpoint returning the counters as JSON plus per-second rates computed by a 60s interval timer (diff of two snapshots). Single-process demo, so counters aggregate all worker threads. - docs/cn/HttpServer.md: document the hooks, getStat and HttpServerStat, noting the per-process semantics. Co-authored-by: TRAE CLI <traecli@bytedance.com>
There was a problem hiding this comment.
🟡 Changes recommended
Request counting, receive-byte accounting, and HTTPS pre-handshake hook timing have unresolved issues.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds HttpServer connection hooks and atomic runtime statistics, with a /stats example endpoint and documentation.
Changes:
- Adds
onAccept/onCloselifecycle hooks and connection tracking. - Adds request and byte counters with rate calculations.
- Preserves streaming callbacks and documents per-process semantics.
File summaries
| File | Summary |
|---|---|
http/server/HttpServer.h |
Declares hooks, statistics, and getStat(). |
http/server/HttpServer.cpp |
Implements lifecycle callbacks and accounting. |
http/server/HttpHandler.h |
Adds the server back-reference. |
http/server/HttpHandler.cpp |
Counts completed requests. |
examples/http_server_test.cpp |
Adds the /stats endpoint and rate calculations. |
docs/cn/HttpServer.md |
Documents the new APIs and semantics. |
Review details
Suppressed comments (2)
http/server/HttpHandler.cpp:391
- Proxy requests return from the earlier
if (proxy)branch inonMessageComplete()before reaching this block, so forward-proxy, reverse-proxy, and CONNECT requests never incrementtotal_requests. Move the increment to the appropriate completion point before that return, while ensuring each keep-alive request is counted once.
// stat
if (server) {
server->stat.total_requests++;
}
http/server/HttpServer.cpp:34
- This only counts reads while
on_recvis installed. Proxy and CONNECT handling later replace the client read callback withhio_write_upstream, so request-body and tunnel bytes bypass this increment andtotal_recv_bytesunder-reports connection traffic. Count reads below the callback replacement or route the forwarding callback through the same accounting path.
if (handler->server) {
handler->server->stat.total_recv_bytes += readbytes;
}
- Files reviewed: 6/6 changed files
- Comments generated: 1
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
- move total_requests++ to the entry of onMessageComplete so proxy and error requests (which return early) are also counted, once per completed request. (review: proxy requests were skipped) - docs/cn/HttpServer.md: remove the incorrect note claiming onAccept runs before the TLS handshake; on https it is invoked after the handshake. NOTE: proxy/CONNECT tunnel forwarding bytes are still not counted in total_recv_bytes/total_send_bytes; left for a future change if needed. Co-authored-by: TRAE CLI <traecli@bytedance.com>
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical and moderate findings affect router lifetime, HTTPS hooks, and proxy traffic accounting.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
http/server/HttpServer.cpp:34
- This only counts reads while the connection uses
on_recv. After a CONNECT or upgraded proxy upstream is established,HttpHandlerreplaces the client read callback withhio_write_upstream(HttpHandler.cpp:1223-1239), which forwards subsequent tunnel bytes without callingon_recv. Consequentlytotal_recv_bytesunderreports that traffic despite being documented as cumulative received bytes; account for forwarded client reads too.
http/server/HttpServer.cpp:101
- On Unix HTTPS listeners,
event/nio.c:160-188runsssl_server_handshakebefore dispatching the server accept callback, so thisonAccepthook runs only after a successful TLS handshake. That defeats the advertised pre-TLS IP filtering/accounting and still lets rejected clients consume handshake resources; invoke a separate pre-handshake hook in the accept/backend path while retaining the HTTP setup after handshake.
if (server->onAccept) {
if (!server->onAccept(io)) {
hio_close(io);
return;
}
- Files reviewed: 6/6 changed files
- Comments generated: 2
- Review effort level: Lite
| }); | ||
|
|
||
| HttpServer server; | ||
| HttpServer& server = g_server; |
| // NOTE: set write_cb before new HttpHandler (which creates the writer | ||
| // Channel that would otherwise install its own write_cb); on_send then | ||
| // owns the slot and still dispatches the writer's onwrite. | ||
| hio_setcb_write(io, on_send); |
What
Adds connection-level hooks and runtime statistics to
HttpServer— capabilities the existing per-request handler chain (headerHandler/preprocessor/middleware/postprocessor) cannot express, because they need the accept/close lifecycle and internal counters.Changes
Connection hooks (
http_server_t)std::function<bool(hio_t* io)> onAccept— called on a new connection, before any HTTP parsing (and before the TLS handshake on https). Returningfalserejects/closes the connection. Enables IP allow/deny and per-connection accounting at the cheapest point.std::function<void(hio_t* io)> onClose— pairs withonAcceptfor the connection lifecycle (e.g. per-IP connection counting /limit_conn).Runtime stats (
HttpServerStat, embedded inhttp_server_t)cur_connections,total_connections,total_requests,total_recv_bytes,total_send_bytes— allstd::atomic<uint64_t>.const HttpServerStat& HttpServer::getStat().cur_connections); compute QPS/throughput by sampling twice and dividing by elapsed time.Implementation notes
connectionNum()now readsstat.cur_connections(atomic) instead of lockingprivdata->mutex_and summing per-loop counts.HttpServerown the iowrite_cb(on_send). TheHttpResponseWriter(aChannel) is still located viahio_contextand itsonwriteis dispatched fromon_send, so streaming paths (sendfile / SSE) keep working — verified a 6MB file download returns intact.on_recv.Example + docs
examples/http_server_test.cpp:/statsendpoint returns the counters as JSON, plus per-second rates (connections_per_sec/requests_per_sec/recv_bytes_per_sec/send_bytes_per_sec) computed by a 60shv::setIntervaltimer diffing two snapshots. This is the single-process demo, so counters aggregate all worker threads and/statsreflects the whole server. (Deliberately not added toexamples/httpd, which defaults to multi-process where/statswould only reflect one worker process.)docs/cn/HttpServer.md: documents the hooks,getStatandHttpServerStat, including the per-process note.Testing
make libhv✅,make examples✅bin/http_server_test, verified/statsJSON:total_connections/total_requests/total_recv_bytes/total_send_bytesincrement correctly,cur_connectionsreturns to baseline after connections close, and per-second rates compute correctly under load.on_sendowning the write_cb does not breakwriter->onwrite.