go net/http/httptrace:内部nettrace泄漏到其他net.Dial调用

ioekq8ef  于 2个月前  发布在  Go
关注(0)|答案(8)|浏览(38)

你正在使用的Go版本是什么(go version)?
go版本:go1.10.2 linux/amd64

这个问题在最新版本的发布中是否会重现?
是的

你做了什么?
运行以下程序

package main

import (
	"context"
	"fmt"
	"net"
	"net/http"
	"net/http/httptrace"
)

func main() {
	dialFunc := func(ctx context.Context, network string, addr string) (net.Conn, error) {
		// This would be where there's a net.Dial to an external lookup service
		// but just pretend that this actually matters for the purposes of this bug
		// report
		n, err := (&net.Dialer{}).DialContext(ctx, "tcp", "www.golang.org:80")
		if err != nil {
			panic(err)
		}
		n.Close()
		// assume that 1.1.1.1 is an IP returned from the lookup service
		// This IP was purely chosen because it's an IP that I know runs a
		// HTTP server
		return (&net.Dialer{}).DialContext(ctx, network, "1.1.1.1:80")
	}

	transport := &http.Transport{
		DialContext: dialFunc,
	}
	req, err := http.NewRequest("GET", "http://example.org/", nil)
	if err != nil {
		panic(err)
	}
	ct := &httptrace.ClientTrace{
		DNSStart: func(info httptrace.DNSStartInfo) {
			fmt.Println("DNSStart:", info.Host)
		},
	}
	ctx := httptrace.WithClientTrace(context.Background(), ct)
	req = req.WithContext(ctx)
	resp, err := transport.RoundTrip(req)
	if err != nil {
		panic(err)
	}
	resp.Body.Close()
}

你期望看到什么?
stdout上没有打印任何内容

你实际上看到了什么?
DNSStart: www.golang.org 被打印在stdout上
此外,ConnectStart和ConnectDone被调用了两次,一次是为了解析服务,另一次是为了实际的HTTP连接。这也会让你非常恼火,一旦你的解析服务可能正在使用HTTP本身,就会导致更多的困惑。
通常情况下,我会在解析函数运行时将ClientTrace存储起来,并独立地调用DNSStart和DNSDone函数,但是一旦将ClientTrace放入上下文中,就没有方法可以将其删除。
我对解决这个问题相当有信心,但这种交互方式是意料之外的,如果你们在使用ClientTraces的库时没有预料到会被覆盖的Dial函数,可能会引起混乱。

jdgnovmf

jdgnovmf2#

我并不完全相信这是一个bug。Transport.DialFunc是用于Transport的。如果你有一些无关的拨号操作,使用一个无关的上下文?

sshcrbum

sshcrbum3#

对于错误报告,这个拨号没有连接,但是在我编写的程序中,我们使用网络名称解析从主机名到IP。在这种情况下,我想通过上下文启用取消操作,而不会得到令人困惑的httptrace回调。

piah890a

piah890a4#

关于:

package httptrace
func WithoutClientTrace(context.Context) context.Context { ... }
hsvhsicv

hsvhsicv5#

或者让 WithClientTrace 执行一个空操作 *ClientTrace 以从上下文中删除它。

axr492tv

axr492tv6#

这可以工作,因为nil已经被保留了:

func WithClientTrace(ctx context.Context, trace *ClientTrace) context.Context {
	if trace == nil {
		panic("nil trace")
	}
...

想要发送一个CL,包括测试和文档吗?

qyswt5oh

qyswt5oh7#

可以做到,可能会在明天寄出去。

r6vfmomb

r6vfmomb8#

我遇到了这个问题,发现这个仍然打开着,所以我别无选择,只能实施一个黑客:

import (
	"context"
	"net"
	"net/http/httptrace"
	"reflect"
)

var stdNetTraceKey, stdHttpTraceKey interface{}

type captureContext struct {
	context.Context
	capture func(reflect.Type)
}

func (c captureContext) Value(key interface{}) interface{} {
	c.capture(reflect.TypeOf(key))
	return nil
}

func init() {
	var stdNetTraceType, stdHttpTraceType reflect.Type

	capture := captureContext{context.Background(), nil}
	capture.capture = func(t reflect.Type) { stdNetTraceType = t }
	(&net.Dialer{}).DialContext(capture, "invalid", "")
	capture.capture = func(t reflect.Type) { stdHttpTraceType = t }
	httptrace.ContextClientTrace(capture)

	stdNetTraceKey = reflect.New(stdNetTraceType).Elem().Interface()
	stdHttpTraceKey = reflect.New(stdHttpTraceType).Elem().Interface()
}

func shadowStandardClientTrace(ctx context.Context) context.Context {
	ctx = context.WithValue(ctx, stdHttpTraceKey, nil)
	ctx = context.WithValue(ctx, stdNetTraceKey, nil)
	return ctx
}

相关问题