From f2e7fa6d31143b2d9fbb94df7aa05b3a0919d112 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Wed, 3 Dec 2025 20:33:09 +0000 Subject: [PATCH] net/http/httptrace: compose ClientTrace without reflect This is to enable use of package httptrace on TinyGo, which does not have an implementation of reflect.MakeFunc. Change-Id: I4f1f94352797bfe3553c1c2d4d1d3dca44b03e43 GitHub-Last-Rev: 4587368ad875bd55e6a8cbdcd7e13738a8d02400 GitHub-Pull-Request: golang/go#69172 Reviewed-on: https://go-review.googlesource.com/c/go/+/609763 Reviewed-by: Damien Neil Reviewed-by: Carlos Amedee LUCI-TryBot-Result: Go LUCI Reviewed-by: Sean Liao --- src/net/http/httptrace/trace.go | 105 ++++++++++++++++++++++++-------- 1 file changed, 78 insertions(+), 27 deletions(-) diff --git a/src/net/http/httptrace/trace.go b/src/net/http/httptrace/trace.go index cee13d2da8..e835c32404 100644 --- a/src/net/http/httptrace/trace.go +++ b/src/net/http/httptrace/trace.go @@ -12,7 +12,6 @@ import ( "internal/nettrace" "net" "net/textproto" - "reflect" "time" ) @@ -176,34 +175,86 @@ func (t *ClientTrace) compose(old *ClientTrace) { if old == nil { return } - tv := reflect.ValueOf(t).Elem() - ov := reflect.ValueOf(old).Elem() - structType := tv.Type() - for i := 0; i < structType.NumField(); i++ { - tf := tv.Field(i) - hookType := tf.Type() - if hookType.Kind() != reflect.Func { - continue - } - of := ov.Field(i) - if of.IsNil() { - continue - } - if tf.IsNil() { - tf.Set(of) - continue - } + t.GetConn = compose1to0(t.GetConn, old.GetConn) + t.GotConn = compose1to0(t.GotConn, old.GotConn) + t.PutIdleConn = compose1to0(t.PutIdleConn, old.PutIdleConn) + t.GotFirstResponseByte = compose0to0(t.GotFirstResponseByte, old.GotFirstResponseByte) + t.Got100Continue = compose0to0(t.Got100Continue, old.Got100Continue) + t.Got1xxResponse = compose2to1(t.Got1xxResponse, old.Got1xxResponse) + t.DNSStart = compose1to0(t.DNSStart, old.DNSStart) + t.DNSDone = compose1to0(t.DNSDone, old.DNSDone) + t.ConnectStart = compose2to0(t.ConnectStart, old.ConnectStart) + t.ConnectDone = compose3to0(t.ConnectDone, old.ConnectDone) + t.TLSHandshakeStart = compose0to0(t.TLSHandshakeStart, old.TLSHandshakeStart) + t.TLSHandshakeDone = compose2to0(t.TLSHandshakeDone, old.TLSHandshakeDone) + t.WroteHeaderField = compose2to0(t.WroteHeaderField, old.WroteHeaderField) + t.WroteHeaders = compose0to0(t.WroteHeaders, old.WroteHeaders) + t.Wait100Continue = compose0to0(t.Wait100Continue, old.Wait100Continue) + t.WroteRequest = compose1to0(t.WroteRequest, old.WroteRequest) +} - // Make a copy of tf for tf to call. (Otherwise it - // creates a recursive call cycle and stack overflows) - tfCopy := reflect.ValueOf(tf.Interface()) +func compose0to0[F func()](f1, f2 F) F { + if f1 == nil { + return f2 + } + if f2 == nil { + return f1 + } + return func() { + f1() + f2() + } +} + +func compose1to0[F func(A), A any](f1, f2 F) F { + if f1 == nil { + return f2 + } + if f2 == nil { + return f1 + } + return func(a A) { + f1(a) + f2(a) + } +} - // We need to call both tf and of in some order. - newFunc := reflect.MakeFunc(hookType, func(args []reflect.Value) []reflect.Value { - tfCopy.Call(args) - return of.Call(args) - }) - tv.Field(i).Set(newFunc) +func compose2to0[F func(A, B), A, B any](f1, f2 F) F { + if f1 == nil { + return f2 + } + if f2 == nil { + return f1 + } + return func(a A, b B) { + f1(a, b) + f2(a, b) + } +} + +func compose2to1[F func(A, B) R, A, B, R any](f1, f2 F) F { + if f1 == nil { + return f2 + } + if f2 == nil { + return f1 + } + return func(a A, b B) R { + f1(a, b) + return f2(a, b) + } +} + +func compose3to0[F func(A, B, C), A, B, C any](f1, f2 F) F { + if f1 == nil { + return f2 + } + if f2 == nil { + return f1 + } + return func(a A, b B, c C) { + f1(a, b, c) + f2(a, b, c) } } -- 2.52.0