Skip to content

Commit aa1af53

Browse files
committed
api: Add all in-flight requests /reverse_proxy/upstreams (#7277)
1 parent 65e0ddc commit aa1af53

4 files changed

Lines changed: 79 additions & 11 deletions

File tree

modules/caddyhttp/reverseproxy/admin.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,33 @@ func (adminUpstreams) handleUpstreams(w http.ResponseWriter, r *http.Request) er
102102
})
103103
return true
104104
})
105+
// Iterate over the inflight hosts
106+
inflightHosts.Range(func(key, val any) bool {
107+
address, ok := key.(string)
108+
if !ok {
109+
rangeErr = caddy.APIError{
110+
HTTPStatus: http.StatusInternalServerError,
111+
Err: fmt.Errorf("could not type assert upstream address"),
112+
}
113+
return false
114+
}
115+
116+
upstream, ok := val.(*Host)
117+
if !ok {
118+
rangeErr = caddy.APIError{
119+
HTTPStatus: http.StatusInternalServerError,
120+
Err: fmt.Errorf("could not type assert upstream struct"),
121+
}
122+
return false
123+
}
124+
125+
results = append(results, upstreamStatus{
126+
Address: address,
127+
NumRequests: upstream.NumRequests(),
128+
Fails: upstream.Fails(),
129+
})
130+
return true
131+
})
105132

106133
// If an error happened during the range, return it
107134
if rangeErr != nil {

modules/caddyhttp/reverseproxy/hosts.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,16 @@ func (u *Upstream) fillHost() {
132132
u.Host = host
133133
}
134134

135+
func (u *Upstream) fillInfilghtHost(numRemaiRequests int) {
136+
host := new(Host)
137+
existingHost, loaded := inflightHosts.LoadOrStore(u.String(), host)
138+
if loaded {
139+
host = existingHost.(*Host)
140+
}
141+
_ = host.countRequest(numRemaiRequests)
142+
u.Host = host
143+
}
144+
135145
// Host is the basic, in-memory representation of the state of a remote host.
136146
// Its fields are accessed atomically and Host values must not be copied.
137147
type Host struct {
@@ -268,6 +278,10 @@ func GetDialInfo(ctx context.Context) (DialInfo, bool) {
268278
// through config reloads.
269279
var hosts = caddy.NewUsagePool()
270280

281+
// inflightHosts is the global repository for hosts that are
282+
// currently in use by inflight upstream request.
283+
var inflightHosts = caddy.NewUsagePool()
284+
271285
// dialInfoVarKey is the key used for the variable that holds
272286
// the dial info for the upstream connection.
273287
const dialInfoVarKey = "reverse_proxy.dial_info"

modules/caddyhttp/reverseproxy/reverseproxy.go

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,9 @@ func (h *Handler) Cleanup() error {
394394

395395
// remove hosts from our config from the pool
396396
for _, upstream := range h.Upstreams {
397+
if upstream.NumRequests() > 0 {
398+
upstream.fillInfilghtHost(upstream.NumRequests())
399+
}
397400
_, _ = hosts.Delete(upstream.String())
398401
}
399402

@@ -458,8 +461,16 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
458461
}
459462

460463
var done bool
461-
done, proxyErr = h.proxyLoopIteration(clonedReq, r, w, proxyErr, start, retries, repl, reqHeader, reqHost, next)
464+
done, dialInfo, proxyErr := h.proxyLoopIteration(clonedReq, r, w, proxyErr, start, retries, repl, reqHeader, reqHost, next)
462465
if done {
466+
key := dialInfo.Address
467+
val := inflightHosts.Load(key)
468+
if val != nil {
469+
host, _ := val.(*Host)
470+
if host.NumRequests() <= 0 {
471+
_, _ = inflightHosts.Delete(key)
472+
}
473+
}
463474
break
464475
}
465476
if h.VerboseLogs {
@@ -490,7 +501,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
490501
// be assigned to the proxyErr value for the next iteration of the loop (or the error handled after break).
491502
func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Request, w http.ResponseWriter, proxyErr error, start time.Time, retries int,
492503
repl *caddy.Replacer, reqHeader http.Header, reqHost string, next caddyhttp.Handler,
493-
) (bool, error) {
504+
) (bool, *DialInfo, error) {
494505
// get the updated list of upstreams
495506
upstreams := h.Upstreams
496507
if h.DynamicUpstreams != nil {
@@ -524,17 +535,17 @@ func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Request, w h
524535
proxyErr = caddyhttp.Error(http.StatusServiceUnavailable, errNoUpstream)
525536
}
526537
if !h.LoadBalancing.tryAgain(h.ctx, start, retries, proxyErr, r, h.logger) {
527-
return true, proxyErr
538+
return true, nil, proxyErr
528539
}
529-
return false, proxyErr
540+
return false, nil, proxyErr
530541
}
531542

532543
// the dial address may vary per-request if placeholders are
533544
// used, so perform those replacements here; the resulting
534545
// DialInfo struct should have valid network address syntax
535546
dialInfo, err := upstream.fillDialInfo(repl)
536547
if err != nil {
537-
return true, fmt.Errorf("making dial info: %v", err)
548+
return true, nil, fmt.Errorf("making dial info: %v", err)
538549
}
539550

540551
if c := h.logger.Check(zapcore.DebugLevel, "selected upstream"); c != nil {
@@ -574,26 +585,26 @@ func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Request, w h
574585
if proxyErr == nil || errors.Is(proxyErr, context.Canceled) {
575586
// context.Canceled happens when the downstream client
576587
// cancels the request, which is not our failure
577-
return true, nil
588+
return true, &dialInfo, nil
578589
}
579590

580591
// if the roundtrip was successful, don't retry the request or
581592
// ding the health status of the upstream (an error can still
582593
// occur after the roundtrip if, for example, a response handler
583594
// after the roundtrip returns an error)
584595
if succ, ok := proxyErr.(roundtripSucceededError); ok {
585-
return true, succ.error
596+
return true, &dialInfo, succ.error
586597
}
587598

588599
// remember this failure (if enabled)
589600
h.countFailure(upstream)
590601

591602
// if we've tried long enough, break
592603
if !h.LoadBalancing.tryAgain(h.ctx, start, retries, proxyErr, r, h.logger) {
593-
return true, proxyErr
604+
return true, &dialInfo, proxyErr
594605
}
595606

596-
return false, proxyErr
607+
return false, &dialInfo, proxyErr
597608
}
598609

599610
// Mapping of the canonical form of the headers, to the RFC 6455 form,
@@ -829,8 +840,14 @@ func (h Handler) addForwardedHeaders(req *http.Request) error {
829840
func (h *Handler) reverseProxy(rw http.ResponseWriter, req *http.Request, origReq *http.Request, repl *caddy.Replacer, di DialInfo, next caddyhttp.Handler) error {
830841
_ = di.Upstream.Host.countRequest(1)
831842
//nolint:errcheck
832-
defer di.Upstream.Host.countRequest(-1)
833-
843+
defer func() {
844+
di.Upstream.Host.countRequest(-1)
845+
inflightHost := inflightHosts.Load(di.Address)
846+
if inflightHost != nil {
847+
host, _ := inflightHost.(*Host)
848+
host.countRequest(-1)
849+
}
850+
}()
834851
// point the request to this upstream
835852
h.directRequest(req, di)
836853

usagepool.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,16 @@ func (up *UsagePool) Delete(key any) (deleted bool, err error) {
194194
return deleted, err
195195
}
196196

197+
func (up *UsagePool) Load(key any) any {
198+
up.RLock()
199+
defer up.RUnlock()
200+
upv, loaded := up.pool[key]
201+
if loaded {
202+
return upv.value
203+
}
204+
return nil
205+
}
206+
197207
// References returns the number of references (count of usages) to a
198208
// key in the pool, and true if the key exists, or false otherwise.
199209
func (up *UsagePool) References(key any) (int, bool) {

0 commit comments

Comments
 (0)