Skip to content

feat(http): add HttpServer runtime stats and onAccept/onClose hooks - #881

Merged
ithewei merged 2 commits into
masterfrom
feat-http-server-stat-hooks
Sep 17, 2026
Merged

ithewei merged 2 commits into
masterfrom
feat-http-server-stat-hooks

Conversation

@ithewei

@ithewei ithewei commented Sep 17, 2026

Copy link
Copy Markdown
Owner

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). Returning false rejects/closes the connection. Enables IP allow/deny and per-connection accounting at the cheapest point.
  • std::function<void(hio_t* io)> onClose — pairs with onAccept for the connection lifecycle (e.g. per-IP connection counting / limit_conn).

Runtime stats (HttpServerStat, embedded in http_server_t)

  • cur_connections, total_connections, total_requests, total_recv_bytes, total_send_bytes — all std::atomic<uint64_t>.
  • Exposed via const HttpServerStat& HttpServer::getStat().
  • Counters are cumulative/monotonic (except cur_connections); compute QPS/throughput by sampling twice and dividing by elapsed time.
  • Per-process semantics: under multi-process mode each process has its own counters; aggregation across processes is left to an external collector (documented).

Implementation notes

  • connectionNum() now reads stat.cur_connections (atomic) instead of locking privdata->mutex_ and summing per-loop counts.
  • Send bytes are counted by having HttpServer own the io write_cb (on_send). The HttpResponseWriter (a Channel) is still located via hio_context and its onwrite is dispatched from on_send, so streaming paths (sendfile / SSE) keep working — verified a 6MB file download returns intact.
  • Recv bytes counted in on_recv.

Example + docs

  • examples/http_server_test.cpp: /stats endpoint 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 60s hv::setInterval timer diffing two snapshots. This is the single-process demo, so counters aggregate all worker threads and /stats reflects the whole server. (Deliberately not added to examples/httpd, which defaults to multi-process where /stats would only reflect one worker process.)
  • docs/cn/HttpServer.md: documents the hooks, getStat and HttpServerStat, including the per-process note.

Testing

  • make libhv ✅, make examples ✅
  • Ran bin/http_server_test, verified /stats JSON: total_connections/total_requests/total_recv_bytes/total_send_bytes increment correctly, cur_connections returns to baseline after connections close, and per-second rates compute correctly under load.
  • Verified a large-file (sendfile) download returns the full body, i.e. on_send owning the write_cb does not break writer->onwrite.

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>
Copilot AI lite review requested due to automatic review settings September 17, 2026 11:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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/onClose lifecycle 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 in onMessageComplete() before reaching this block, so forward-proxy, reverse-proxy, and CONNECT requests never increment total_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_recv is installed. Proxy and CONNECT handling later replace the client read callback with hio_write_upstream, so request-body and tunnel bytes bypass this increment and total_recv_bytes under-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.

Comment thread http/server/HttpServer.cpp
- 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>
Copilot AI review requested due to automatic review settings September 17, 2026 11:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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, HttpHandler replaces the client read callback with hio_write_upstream (HttpHandler.cpp:1223-1239), which forwards subsequent tunnel bytes without calling on_recv. Consequently total_recv_bytes underreports 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-188 runs ssl_server_handshake before dispatching the server accept callback, so this onAccept hook 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;
Comment on lines +109 to +112
// 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);
@ithewei
ithewei merged commit d634fcb into master Sep 17, 2026
13 checks passed
@ithewei
ithewei deleted the feat-http-server-stat-hooks branch September 17, 2026 14:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants