feat: add trusted proxy identity and overload stats

This commit is contained in:
2026-09-22 18:19:39 +08:00
parent 2259606f72
commit 95dc4568f6
17 changed files with 346 additions and 42 deletions

View File

@@ -53,27 +53,32 @@ type Server struct {
// when the listener and TLS configuration are assembled by the caller.
TLSConfig *tls.Config
mu sync.Mutex
handler http.Handler
listener net.Listener
conns map[*connection]struct{}
closing bool
cancel context.CancelFunc
wg sync.WaitGroup
connSlots chan struct{}
requestSlots chan struct{}
accepted atomic.Uint64
activeConns atomic.Int64
activeRequests atomic.Int64
readerPool sync.Pool
writerPool sync.Pool
connectionPool sync.Pool
mu sync.Mutex
handler http.Handler
listener net.Listener
conns map[*connection]struct{}
closing bool
cancel context.CancelFunc
wg sync.WaitGroup
connSlots chan struct{}
requestSlots chan struct{}
accepted atomic.Uint64
rejectedConns atomic.Uint64
rejectedRequests atomic.Uint64
activeConns atomic.Int64
activeRequests atomic.Int64
readerPool sync.Pool
writerPool sync.Pool
connectionPool sync.Pool
}
// Stats is a point-in-time view of the native server lifecycle. Counters are
// monotonic unless their name starts with Active.
type Stats struct {
AcceptedConnections uint64 `json:"acceptedConnections"`
// Rejections count admission decisions, even if writing a 503 fails.
RejectedConnections uint64 `json:"rejectedConnections"`
RejectedRequests uint64 `json:"rejectedRequests"`
ActiveConnections int64 `json:"activeConnections"`
ActiveRequests int64 `json:"activeRequests"`
}
@@ -86,6 +91,8 @@ func (s *Server) Stats() Stats {
}
return Stats{
AcceptedConnections: s.accepted.Load(),
RejectedConnections: s.rejectedConns.Load(),
RejectedRequests: s.rejectedRequests.Load(),
ActiveConnections: s.activeConns.Load(),
ActiveRequests: s.activeRequests.Load(),
}
@@ -252,6 +259,7 @@ func (s *Server) Serve(ctx context.Context, listener net.Listener) error {
select {
case slots <- struct{}{}:
default:
s.rejectedConns.Add(1)
_ = conn.Close()
continue
}
@@ -424,6 +432,7 @@ func (s *Server) serveConnection(baseCtx context.Context, state *connection) {
s.activeRequests.Add(-1)
return
default:
s.rejectedRequests.Add(1)
writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
writer.WriteHeader(http.StatusServiceUnavailable)
writer.body.WriteString("server busy\n")

View File

@@ -711,6 +711,9 @@ func TestServerConcurrentRequestLimit(t *testing.T) {
if response.StatusCode != http.StatusServiceUnavailable {
t.Fatalf("expected 503 at concurrency limit, got %d", response.StatusCode)
}
if stats := server.Stats(); stats.RejectedRequests != 1 {
t.Fatalf("expected one rejected request, got %+v", stats)
}
close(release)
response, err = http.ReadResponse(bufio.NewReader(first), &http.Request{Method: http.MethodGet})
if err != nil {
@@ -842,6 +845,9 @@ func TestServerConnectionLimitReleasesAfterRequest(t *testing.T) {
t.Fatal("connection over MaxConnections was not rejected")
}
second.Close()
if stats := server.Stats(); stats.RejectedConnections < 1 {
t.Fatalf("expected rejected connection counter, got %+v", stats)
}
close(release)
firstResponse, err := http.ReadResponse(bufio.NewReader(first), &http.Request{Method: http.MethodGet})