原创分享 Go HTTP 默认的 Client 对象使用注意事项

jemygraw · 2020年07月01日 · 最后由 cch123 回复于 2020年07月03日 · 565 次阅读

前言

前面写过一篇帖子介绍了 Go HTTP 中重用 TCP 连接需要注意的问题,看上去阅读量很好。所有打算再介绍下 Go HTTP 中使用默认的 Client 时需要注意的事项。

问题

在一个系统中,通过 HTTP 调用第三方的服务是很常见的,而且稍微大的一点的系统,可能需要调用不同的组件来完成一项工作。这个时候就需要注意一些细节问题。

我们知道 Go 的net/http库里面有很多方法可以直接使用。比如常见的 http.Gethttp.Post。一般情况下我们用这两个方法来写个脚本什么的都没啥问题。但是需要注意的是在大型系统中,千万不要直接使用这些方法。具体原因就在这些方法的定义中。

http.Get

func Get(url string) (resp *Response, err error) {
    return DefaultClient.Get(url)
}

http.Post

func Post(url, contentType string, body io.Reader) (resp *Response, err error) {
    return DefaultClient.Post(url, contentType, body)
}

http.DefaultClient

上面的两个方法里面都使用了http.DefaultClient对象,这个对象是net/http包里面提供的可以即时使用的 HTTP Client 对象,它的定义如下:

// DefaultClient is the default Client and is used by Get, Head, and Post.
var DefaultClient = &Client{}

问题出在哪里?问题就出在这个DefaultClient是个指针,换句话说这个对象在 Go 的并发编程中是不安全的。

实验

package main

import (
    "fmt"
    "net/http"
    "time"
)

func main() {
    // 初始化超时时间为 1 秒
    http.DefaultClient.Timeout = time.Second
    go func() {
        ticker := time.NewTicker(time.Second * 5)
        count := 1
        for {
            select {
            case <-ticker.C:
                // 每隔 5 秒,更新一下超时时间
                http.DefaultClient.Timeout = time.Second * time.Duration(count)
                count++
            }
        }
    }()

    // 不断请求 Google,会触发超时,如果没有超时,说明你已经违法,😄
    for i := 0; i < 100; i++ {
        startTime := time.Now()
        func() {
            resp, err := http.Get("https://www.google.com")
            if err != nil {
                return
            }
            defer resp.Body.Close()
        }()

        // 打印下运行数据,开始时间,超时时间
        fmt.Println(fmt.Sprintf("Run %d:", i+1), "Start:", startTime.Format("15:04:05"),
            "Timeout:", time.Since(startTime))

        // 每隔 1 秒请求一次
        <-time.After(time.Second)
    }
}

运行情况:

Run 1: Start: 21:37:42 Timeout: 1.002390001s
Run 2: Start: 21:37:44 Timeout: 1.005189409s
Run 3: Start: 21:37:46 Timeout: 1.001791553s
Run 4: Start: 21:37:48 Timeout: 1.000847131s
Run 5: Start: 21:37:50 Timeout: 1.0042284s
Run 6: Start: 21:37:52 Timeout: 2.001313209s
Run 7: Start: 21:37:55 Timeout: 2.000255175s
Run 8: Start: 21:37:58 Timeout: 3.005502974s
Run 9: Start: 21:38:02 Timeout: 4.005494172s
Run 10: Start: 21:38:07 Timeout: 5.001988372s
Run 11: Start: 21:38:13 Timeout: 6.000908119s
Run 12: Start: 21:38:20 Timeout: 7.003262543s
Run 13: Start: 21:38:28 Timeout: 9.000410503s
Run 14: Start: 21:38:38 Timeout: 11.004758151s
Run 15: Start: 21:38:50 Timeout: 13.002290813s

其实不需要这个例子,你也能够明白在并发环境不能直接用共享的变量,否则会出问题的。比如 A 系统超时时间和 B 系统超时时间完全不同,结果因为用了共享的 http.DefaultClient,就混在一起了。

小结

学无止境,小心翼翼。

备份链接:Go HTTP 默认的 Client 对象使用注意事项

更多原创文章干货分享,请关注公众号
  • 加微信实战群请加微信(注明:实战群):gocnio

blog 链接发一下呗

wayne502 回复

我加了下备份链接。

DefaultClient 是一个全局的默认值,本来就不应该在使用中修改它,最好初始化时修改后不再动它了

这些经验基本都是使用默认 http 库的坑

生产环境由框架开发组统一提供一个公用库就可以了,没必要让所有人都踩一遍坑,同理还有 db 相关

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册