前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Go语言中常见100问题-#81 Using the default HTTP client and server

Go语言中常见100问题-#81 Using the default HTTP client and server

作者头像
数据小冰
发布2022-08-15 15:29:58
1.3K0
发布2022-08-15 15:29:58
举报
文章被收录于专栏:数据小冰
不要使用默认的HTTP client和 HTTP server

Go标准库中的http包提供了HTTP客户端和服务器实现。但是,开发人员很容易犯一个常见错误:最终部署到生产环境中的应用程序的上下文依赖于默认实现。本文将分析这会产生什么问题以及如何解决。

HTTP Client

首先,来看看HTTP客户端默认值的含义,这里以GET请求为例进行说明。客户端默认值就是创建一个http.Client的零值,像下面的程序,初始化时没有设置任何参数。

代码语言:javascript
复制
client := &http.Client{}
resp, err := client.Get("https://golang.org/")

或者直接使用http.Get方法进行请求

代码语言:javascript
复制
resp, err := http.Get("https://golang.org/")

这两种Get请求本质实现是一样的,像http.Get这样底层使用的是http.DefaultClient,它也是基于http.Client创建的一个零值对象。

代码语言:javascript
复制
// DefaultClient is the default Client and is used by Get, Head, and Post.
var DefaultClient = &Client{}

使用默认的HTTP客户端有什么问题吗?首先,默认客户端没有设置任何超时时长,在生产环境中没有超时限制是可怕的,可能会导致很多问题。例如无止境的请求可能耗尽系统资源。在深入研究请求超时问题之前,让我们先来回顾一下HTTP请求中涉及的五个步骤:

  • 建立TCP连接
  • 进行TLS握手(如果开启)
  • 发送请求
  • 读取响应消息头
  • 读取响应消息体

下面这幅图描述了上面5个步骤与客户端超时参数的关系:

图中四个超时参数含义如下:

  • net.Dialer.Timeout:等待建立TCP连接的最长时间
  • http.Transport.TLSHandshakeTimeout:等待TLS握手的最长时间
  • http.Transport.ResponseHeaderTimeout:等待服务器响应消息头的时间
  • http.Client.Timeout:整个请求的时间,包含建立TCP连接、进行TLS握手、发送请求、等待响应消息头和消息体的时间。

「NOTE: http请求返回的第二参数error表示未能(按预期时间)收到服务端的响应,此错误来自对消息头的处理,因为等待读取响应消息头是等待响应的第一步。如果设置了http.Client.Timeout, 等待响应消息头时间过长时会遇到如下错误提示」

代码语言:javascript
复制
net/http: request canceled (Client.Timeout exceeded while awaiting headers)

下面是设置了四个超时时间的一个客户端程序示例,该客户端建立TCP连接、TLS握手和读取响应头的设置的超时时间均为1秒,每个请求总的超时时间为5秒。

代码语言:javascript
复制
client := &http.Client{
        Timeout: 5 * time.Second,
        Transport: &http.Transport{
                DialContext: (&net.Dialer{
                        Timeout: time.Second,
                }).DialContext,
                TLSHandshakeTimeout:   time.Second,
                ResponseHeaderTimeout: time.Second,
        },
}

默认情况下,HTTP客户端带有连接池行为。可以重用客户端连接,通过设置http.Transport.DisableKeepAlives为true可以禁用重用功能。此外,还有一个额外的超时来指定空闲连接在连接池中保留的时间,该时间由http.Transport.IdleConnTimeout控制,默认值为90秒,意味着此期间内连接可以被其他请求重用,在90之后如果连接没有被重用,它将被关闭。

如果想要配置连接池中的参数,我们需要重新设置http.Transport.MaxIdleConns,通过下面的程序可以看到该参数的默认为100. 此外,还有一个参数需要注意,它就是http.Transport.MaxIdleConnsPerHost,该参数表示每个host的连接池最大空闲连接数,默认值为2,即如果向同一台主机触发100个请求,之后只有2个连接将保留在连接池中。因此,如果再次触发100个请求,将不得不重新建立至少98个连接。在生产级程序中,我们需要注意该参数配置,因为它会影响平均延迟当同一个主机存在大量并行请求时。

代码语言:javascript
复制
var DefaultTransport RoundTripper = &Transport{
 Proxy: ProxyFromEnvironment,
 DialContext: (&net.Dialer{
  Timeout:   30 * time.Second,
  KeepAlive: 30 * time.Second,
 }).DialContext,
 ForceAttemptHTTP2:     true,
 MaxIdleConns:          100,
 IdleConnTimeout:       90 * time.Second,
 TLSHandshakeTimeout:   10 * time.Second,
 ExpectContinueTimeout: 1 * time.Second,
}

// DefaultMaxIdleConnsPerHost is the default value of Transport's
// MaxIdleConnsPerHost.
const DefaultMaxIdleConnsPerHost = 2

总结,对于生产级程序,我们通常会重新设置上述连接池参数,不会使用标准库中提供的默认值。同时需要注意,调整这些与连接池相关的参数会对延迟产生重大影响,所以在设置时要小心,需要设置合理的值。

HTTP Server

在实现HTTP服务器时,我们也应该小心谨慎。默认的HTTP server可以通过http.Server创建,代码如下:

代码语言:javascript
复制
server := &http.Server{}
server.Serve(listener)

HTTP Server也可以通过一些函数创建,像http.Serve、http.ListenAndServe和http.ListenAndServeTLS. 这些函数内部实现也是依赖于默认的http.Server.

接收客户端连接后,HTTP响应分为五个步骤:

  • 等待客户端发生请求
  • TLS握手(如果启用)
  • 读取请求头(http header)
  • 读取请求正文(http body)
  • 写回复内容

「NOTE: 不必对已建立的连接重复TLS握手过程」

下面这幅图描述了上面步骤中与服务器超时参数的关系:

三个主要的超时参数/函数及含义如下:

  • http.Server.ReadHeaderTimeout: 该参数表示读取请求头的最长时间。
  • http.Server.ReadTimeout: 该参数表示读取整个请求的最长时间(包括等待客户端发送请求、TLS握手、读取请求头和请求正文)
  • http.TimeoutHandler: 该函数是对handler的一个封装,表示处理程序完成读取请求正文和写回复内容的最长时间。

注意这三个参数/函数中的最后一个,它不是服务器参数,只是handler的一个封装,用于限制http处理请求的最长时间。如果处理程序未能按时响应,服务器将回复特定的503 Service Unavailable 消息。同时,传递给处理程序的上下文将被取消。

「NOTE: 上文省略了对http.Server.WriteTimeout 的介绍,因为在Go1.8版本中http.TimeoutHandler发布之后,该参数不是必须要设置的字段。实际上,http.Server.WriteTimeout在使用上有一些问题。首先,它的行为取决于是否启用了TLS, 使得它的理解和使用更加复杂。其次,如果达到超时时间,它会关闭TCP连接而不返回正确的HTTP状态码。此外,它不会将传递给处理程序的上下文取消,这会导致处理程序在不知道TCP连接已经关闭的情况下继续执行。」

如果我们的服务器需要接收来自不受信任的客户端连接时,最佳实践是至少要设置http.Server.ReadHeaderTimeout参数并使用http.TimeoutHandler包装函数。否则,如果客户端可能会利用它并创建大量的连接,从而耗尽服务器资源。

下面是一个设置带有超时服务器的程序示例,通过http.TimeoutHandler包装业务处理程序。在上面这个服务器中,如果处理程序在1秒内没有响应,将会返回HTTP 503状态码。

代码语言:javascript
复制
s := &http.Server{
        Addr:              ":8080",
        ReadHeaderTimeout: 500 * time.Millisecond,
        ReadTimeout:       500 * time.Millisecond,
        Handler:           http.TimeoutHandler(handler, time.Second, "foo"),
}

此外,同客户端配置一样,我们也可以对服务器配置启用keep-alives设置等待下一个请求的最长时间,具体通过http.Server.IdleTimeout参数进行设置。注意,如果没有设置http.Server.IdleTimeout,则会使用http.Server.ReadTimeout的值作为空闲超时时间。如果这两个参数都没有设置,则不会有任何超时,并且连接将保持打开状态,直到它们被客户端关闭。

代码语言:javascript
复制
s := &http.Server{
        // ...
        IdleTimeout: time.Second,
}

总结,对于生产级应用程序,我们不要使用默认的HTTP Client和 HTTP Server. 否则,由于没有设置超时,恶意用户利用服务器没有设置超时这个漏洞,可能会导致服务器卡住无法继续提供服务。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-07-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 数据小冰 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 不要使用默认的HTTP client和 HTTP server
    • HTTP Client
      • HTTP Server
      相关产品与服务
      SSL 证书
      腾讯云 SSL 证书(SSL Certificates)为您提供 SSL 证书的申请、管理、部署等服务,为您提供一站式 HTTPS 解决方案。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档