在Go中获取HTTP响应时间

huangapple go评论146阅读模式
英文:

Time HTTP Response in Go

问题

在我的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
}

实际上,这段代码将显示大约5秒的请求时间,包括DNS解析和其他操作... 如果我使用类似Apache JMeter的工具执行相同的测试,时间只需约100毫秒(这是服务器的真实响应时间,不考虑请求时间)。

我真正想要的是计算服务器的真实响应时间。在Go中,我该如何计算这个时间?

英文:

In my Go program, I made some HTTP requests and I need to time the response time (and not request time).

Here is my current code (timing request time):

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
}

Actually, this code will show something about 5s request time, including DNS resolution and other things... If I execute the same test with a tool like Apache JMeter, the time is just about 100ms (which is the real response time of the server, without taking care about request time).

What I really want is to compute the real response time of the server. How can I compute this in Go ?

答案1

得分: 18

不要削弱完全有效的接受答案的价值,一个值得注意的替代方案是实现一个自定义的RoundTripper,它包装了默认的http.Transport和net.Dialer。如果你正在对使用http.Client的代码进行仪器化,或者如果你需要支持代理、TLS、持久连接或其他HTTP功能,但又不想/不需要重新实现它们,这可能会有所帮助。你不会像完全定制的客户端那样拥有完全的控制权,但它值得放在你的工具箱中。

以下是一个示例的round tripper:

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

英文:

Not to take anything away from the perfectly valid accepted answer, one alternative to be aware of is to implement a custom RoundTripper that wraps the default http.Transport and net.Dialer. This can be helpful if you are instrumenting code that uses http.Client or if you need to support proxies, TLS, keep-alive or other HTTP capabilities but don't want/need to re-implement them all. You won't have quite as much control as you will with a fully customized client but it's worth having in your toolbox.

Example round tripper:

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)
}

I've dropped that into a simple example program here: https://github.com/skyec/go-instrumented-roundtripper/blob/master/main.go

答案2

得分: 15

你可以通过打开一个TCP连接并使用原始的HTTP协议来测量它(参考维基百科和RFC文档)。你建立一个连接,发送请求,并在此处开始计时。然后等待响应。通过这样做,你将排除DNS解析和建立连接所需的时间。

由于你无法得知服务器是立即开始发送数据还是在准备好所有数据后才发送,也无法得知网络延迟的信息,所以这个方法只能给出一个估计值,而不是准确的值。

我建议在两个点上进行测量:当可以读取到第一个字节时,以及当所有数据都被读取时:

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))

注意:

在第三个点上进行测量也是合理的:当读取完整的响应头时。这可以通过从响应中读取完整的行(连接)并遇到一个空行来检测到。这个空行将响应头和响应体分隔开来。

英文:

Edit: The following answer pre-dates Go 1.7. Go 1.7 added HTTP tracing, so be sure to check out this new answer: https://stackoverflow.com/questions/48077098/getting-ttfb-time-to-first-byte-value-in-golang/48077762?r=SearchResults#48077762

Original answer follows.


You can measure it by opening a TCP connection and "speaking" raw HTTP (wikipedia, rfc7230, rfc7231, rfc7232, rfc7233, rfc7234, rfc7235). You make a connection, send the request, and start the timer here. And wait for the response. By doing so you will exclude DNS resolving and the time required to make the connection.

Since you have no insight whether the server starts sending data back immediately or only when everything is ready, and you have no info about network delays, it won't be accurate rather just an estimation.

I would measure at 2 points: when the first byte can be read and when everything is read:

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))

Note:

It might be reasonable to measure at a 3rd point: when all the response headers are read. This is detected as reading full lines from the response (connection) and encountering an empty line. This empty line separates response headers from the response body.

答案3

得分: 2

有一个名为net/http/httptrace的包,自Go 1.7版本开始提供。它支持以下操作的计时:

  • 连接创建
  • 连接重用
  • DNS查找
  • 将请求写入网络
  • 读取响应

另外,你可以查看一下https://github.com/davecheney/httpstat,这是一个用于可视化计时结果的实用工具。

英文:

There is package https://golang.org/pkg/net/http/httptrace/ since go 1.7;
It supports timing of following operations:

  • Connection creation
  • Connection reuse
  • DNS lookups
  • Writing the request to the wire
  • Reading the response

Also take a look at https://github.com/davecheney/httpstat. It's nice utility for visualizing timing results.

huangapple
  • 本文由 发表于 2015年5月29日 18:12:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/30526946.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定