Go中的HTTP debug技能 了解一下

共 8775字,需浏览 18分钟

 ·

2022-06-21 01:18

最近把一个负责的HTTP服务搬上了公司的内部的k8s平台。由于部分HTTP服务还未接入k8s平台,所以内部服务之间的交互主要通过内网域名。说到这里,一天下午小土负责的A服务跟下游服务B的请求一直报警超时,开始小土使用tcpdump定时抓包系统采集了一些pcap文件,由于小土对抓包也不是很在行,HTTP抓包也碰了一鼻子灰。在跟同事交流的同时,想到了httptrace包,下面主要介绍一下httptrace和问题的定位过程。

HTTP trace的介绍

Go官方于2016 年 10 月 4 日发的一篇博文Introducing HTTP Tracing[1]。翻译见 Go的HTTP tracing。文中主要举了两个案例:

1.将包含钩子函数的 httptrace.ClientTrace 放入request的context中进行跟踪;

2.对http.Client进行跟踪,使用 http.RoundTripper wrapper 来标识当前的请求。

Go1.7 引入了HTTP trace,可以在HTTP客户端请求过程中收集一些更细粒度的信息,httptrace包[2]提供了HTTP trace的支持,收集的信息可用于调试延迟问题,服务监控,编写自适应系统等。httptrace包提供了许多钩子,在HTTP往返期间收集各种事件的信息,包括连接的创建、复用、DNS解析查询、写入请求和读取响应。

httptrace包

httptrace包提供了追踪HTTP Client请求事件的机制,主要就是trace.go这个文件。trace_test.go 是对trace.go中的函数的单元测试。example_test.go是对httptrace的例子测试。

大家可以从这里查看trace.go 源码[3]

├── example_test.go
├── trace.go
└── trace_test.go

从官网博文例子中也可以看出使用了ClientTrace这个struct中的一些钩子函数。下面罗列了ClientTrace中的函数列表。

type ClientTrace struct {
 GetConn              func(hostPort string)                             // 在创建连接之前调用
 GotConn              func(GotConnInfo)                                 // 连接成功后调用
 PutIdleConn          func(err error)                                   // 当连接用完时需要放回池子调用
 GotFirstResponseByte func()                                            // 当读到响应的第一个字节时
 Got100Continue       func()                                            // 当收到的状态码是"100",则会调用Got100Continue继续响应
 Got1xxResponse       func(code int, header textproto.MIMEHeader) error // 为每个 1xx 信息响应头调用,Got1xxResponse在最终的非 1xx 响应之前返回。
 DNSStart             func(DNSStartInfo)                                // 当DNS查询开始时调用
 DNSDone              func(DNSDoneInfo)                                 // 当DNS查询结束时调用
 ConnectStart         func(network, addr string)                        // 当一个新连接 Dial开始时
 ConnectDone          func(network, addr string, err error)             // 当一个新的连接的Dial完成时
 TLSHandshakeStart    func()                                            // TLS 握手开始时调用,当通过HTTP代理连接到一个HTTPS站点时,握手发生在代理处理完CONNECT请求之后。
 TLSHandshakeDone     func(tls.ConnectionState, error)                  // 在TLS握手后被调用,其中包括握手成功的连接状态,或者握手失败的非零错误。
 WroteHeaderField     func(key string, value []string)                  // 在传输时write完每个请求头后被调用。
 WroteHeaders         func()                                            // 在传输时write完所有请求头后调用的。
 Wait100Continue      func()                                            // 如果请求时指定了"Expect: 100-continue",且在传输过程中已经写入,但是在写请求正文之前还在等待 "100 Continue"。
 WroteRequest         func(WroteRequestInfo)                            // 在写入请求后调用。在重试请求的情况下,它可以被多次调用。

}

很多同学也应该不是很清楚100状态码的含义,这里科普一下

100状态码 :HTTP100状态码代表的意思是请继续请求,即HTTP 100 Continue 响应状态。HTTP 100 (Http Status Code 100) 状态是HTTP协议的一种响应码,是我们请求访问网站时,服务器端返回的1xx 请求信息系列响应码之一。状态详细说明:HTTP 100 表示客户端应当继续进行请求

引用自HTTP状态码 100 (Continue) 含义详解[4]

问题定位破案

最后是如何定位到问题的呢?想必大家看了上面的一系列介绍,也应该猜到了。主要在内部封装的httpclient中加入了clienttrace,并在请求结束后打印了trace的result,通过重新打包部署到k8s开发环境,等待问题复现并查看日志,最后通过观察traceresult发现是域名解析的时候出现了问题,内网域名解析道是空的ip信息,便把域名解析的问题同步报告给k8s部署平台负责的同事。

下面是工程中的一些代码实现。借此机会小土也准备把httptrace工具集成到公共库中,并可以通过apollo平台配置httptrace开关来开启debug定位具体问题。

func (c *HTTPClient) Do (request *http.Request,traceId string, debug bool) (*Response, error) {
  // ... define
  if debug {
  clientTrace, traceResult = clientTrace(traceId)
  request = request.WithContext(httptrace.WithClientTrace(request.Context(), clientTrace))
  }
  // ... retry
  res,err := c.client.Do(request)
  if debug {
  data, _ := json.Marshal(traceResult)
  log.Debugf("[%v] trace info: %s", traceId,string(data))
 }
  // ... parse
}

func clientTrace(traceId string ) (clientTrace *httptrace.ClientTrace,traceResult *TraceResult) {
 traceResult = &TraceResult{
    TraceId:traceId,
  }

 clientTrace := &httptrace.ClientTrace{
  DNSStart: func(info httptrace.DNSStartInfo) {
   currentTime := time.Now().Local().String()
   traceResult.DNS.Start = currentTime
   traceResult.DNS.Host = info.Host
  },
  DNSDone: func(info httptrace.DNSDoneInfo) {
      currentTime := time.Now().Local().String()
   traceResult.DNS.End = currentTime
   traceResult.DNS.Addrs = info.Addrs
   traceResult.DNS.Err = info.Err
  },
    // ... 省略一些代码
  }
  
 return clientTrace, traceResult
}

小结

这里对httptrace的一些介绍,相信大家也多了一项debug技能,也会对http从请求到响应有了更深的了解。每一个问题都是发现未知的机会,希望gopher们在遇到问题时不要害怕,迎难而上,追根到底。

最后希望这篇短文,能给你带来一些帮助。如有问题可以下方留言讨论。

参考资料

[1]

Introducing HTTP Tracing: https://go.dev/blog/http-tracing

[2]

httptrace包: https://pkg.go.dev/net/http/httptrace

[3]

trace.go 源码: https://github1s.com/golang/go/blob/HEAD/src/net/http/httptrace/trace.go

[4]

HTTP状态码 100 (Continue) 含义详解: https://seo.juziseo.com/doc/http_code/100



推荐阅读


福利

我为大家整理了一份从入门到进阶的Go学习资料礼包,包含学习建议:入门看什么,进阶看什么。关注公众号 「polarisxu」,回复 ebook 获取;还可以回复「进群」,和数万 Gopher 交流学习。

浏览 29
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报