Go中的时间HTTP响应

问题描述 投票:11回答:3

在我的Go程序中,我发出了一些HTTP请求,我需要计算响应时间(而不是请求时间)。

这是我当前的代码(时间请求时间):

func Get() int {
    start := time.Now()
    result, err := http.Get("http://www.google.com")
    if err != nil {
        log.Fatal(err)
    }
    defer result.Body.Close()
    elapsed := time.Since(start).Seconds()
    log.Println(elapsed)

    return result.StatusCode
}

实际上,这段代码将显示5s请求时间,包括DNS解析和其他东西......如果我用Apache JMeter等工具执行相同的测试,时间大约是100ms(这是服务器的实际响应时间) ,不需要关心请求时间)。

我真正想要的是计算服务器的实际响应时间。我怎样才能在Go中计算出来?

http go
3个回答
10
投票

编辑:以下答案早于Go 1.7。 Go 1.7添加了HTTP跟踪,所以请务必查看这个新答案:Getting TTFB (time to first byte) value in golang

原始答案如下。


您可以通过打开TCP连接和“说”原始HTTP(wikipediarfc7230rfc7231rfc7232rfc7233rfc7234rfc7235)来衡量它。您建立连接,发送请求,并在此处启动计时器。并等待回应。通过这样做,您将排除DNS解析和建立连接所需的时间。

由于您无法了解服务器是立即开始发送数据还是仅在所有内容都准备就绪时,并且您没有关于网络延迟的信息,因此它不准确而只是估计。

我会测量2点:当第一个字节可以读取时和读取所有内容时:

conn, err := net.Dial("tcp", "google.com:80")
if err != nil {
    panic(err)
}
defer conn.Close()
conn.Write([]byte("GET / HTTP/1.0\r\n\r\n"))

start := time.Now()
oneByte := make([]byte,1)
_, err = conn.Read(oneByte)
if err != nil {
    panic(err)
}
log.Println("First byte:", time.Since(start))

_, err = ioutil.ReadAll(conn)
if err != nil {
    panic(err)
}
log.Println("Everything:", time.Since(start))

注意:

在第3点进行测量可能是合理的:何时读取所有响应头。这被检测为从响应(连接)读取满行并遇到空行。此空行将响应标头与响应正文分开。


13
投票

不要从完全有效的接受答案中拿走任何东西,另外一个需要注意的方法是实现一个包装默认http.Transport和net.Dialer的自定义RoundTripper。如果要检测使用http.Client的代码,或者需要支持代理,TLS,保持活动或其他HTTP功能但不希望/需要重新实现它们,这可能会有所帮助。使用完全自定义的客户端,您将无法获得尽可能多的控制权,但它在您的工具箱中值得拥有。

圆形脱钩器示例:

type customTransport struct {
    rtp       http.RoundTripper
    dialer    *net.Dialer
    connStart time.Time
    connEnd   time.Time
    reqStart  time.Time
    reqEnd    time.Time
}

func newTransport() *customTransport {
    tr := &customTransport{
        dialer: &net.Dialer{
            Timeout:   30 * time.Second,
            KeepAlive: 30 * time.Second,
        },
    }
    tr.rtp = &http.Transport{
        Proxy:               http.ProxyFromEnvironment,
        Dial:                tr.dial,
        TLSHandshakeTimeout: 10 * time.Second,
    }
    return tr
}

func (tr *customTransport) RoundTrip(r *http.Request) (*http.Response, error) {
    tr.reqStart = time.Now()
    resp, err := tr.rtp.RoundTrip(r)
    tr.reqEnd = time.Now()
    return resp, err
}

func (tr *customTransport) dial(network, addr string) (net.Conn, error) {
    tr.connStart = time.Now()
    cn, err := tr.dialer.Dial(network, addr)
    tr.connEnd = time.Now()
    return cn, err
}

func (tr *customTransport) ReqDuration() time.Duration {
    return tr.Duration() - tr.ConnDuration()
}

func (tr *customTransport) ConnDuration() time.Duration {
    return tr.connEnd.Sub(tr.connStart)
}

func (tr *customTransport) Duration() time.Duration {
    return tr.reqEnd.Sub(tr.reqStart)
}

我把它放到了一个简单的示例程序中:https://github.com/skyec/go-instrumented-roundtripper/blob/master/main.go


1
投票

自1.7以来有包https://golang.org/pkg/net/http/httptrace/;它支持以下操作的时间:

  • 连接创建
  • 连接重用
  • DNS查找
  • 将请求写入电线
  • 阅读回复

还可以看看https://github.com/davecheney/httpstat。它可用于可视化计时结果。

© www.soinside.com 2019 - 2024. All rights reserved.