在Golang中跟踪和调试HTTP客户端请求的方法

868 阅读3分钟

在这个例子中,我们将使用httptrace来获取与HTTP客户端请求有关的信息,并打印出来用于调试目的。追踪将从DNS查询开始,一直到收到响应。最后我们将以JSON格式打印结果。如果你想调试HTTP客户端的问题,这可能是个好办法。

package main

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"net/http/httptrace"
	"time"
)

type Debug struct {
	DNS struct{
		Start   string       `json:"start"`
		End     string       `json:"end"`
		Host    string       `json:"host"`
		Address []net.IPAddr `json:"address"`
		Error   error        `json:"error"`
	} `json:"dns"`
	Dial struct{
		Start string `json:"start"`
		End   string `json:"end"`
	} `json:"dial"`
	Connection struct{
		Time string `json:"time"`
	} `json:"connection"`
	WroteAllRequestHeaders struct{
		Time string `json:"time"`
	} `json:"wrote_all_request_header"`
	WroteAllRequest struct{
		Time string `json:"time"`
	} `json:"wrote_all_request"`
	FirstReceivedResponseByte struct{
		Time string `json:"time"`
	} `json:"first_received_response_byte"`
}

func main() {
	// Create trace struct.
	trace, debug := trace()

	// Prepare request with trace attached to it.
	req, err := http.NewRequest(http.MethodGet, "http://localhost:8090/server/api/v1/users", nil)
	if err != nil {
		log.Fatalln("request error", err)
	}
	req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))

	// MAke a request.
	res, err := client().Do(req)
	if err != nil {
		log.Fatalln("client error", err)
	}
	defer res.Body.Close()

	// Read response.
	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Fatal(err)
	}

	// Print report.
	data, err := json.MarshalIndent(debug, "", "    ")
	fmt.Println(string(data))
	fmt.Println(string(body))
}

func client() *http.Client {
	return &http.Client{
		Transport: transport(),
	}
}

func transport() *http.Transport {
	return &http.Transport{
		DisableKeepAlives: true,
		TLSClientConfig:   tlsConfig(),
	}
}

func tlsConfig() *tls.Config {
	return &tls.Config{
		InsecureSkipVerify: true,
	}
}

func trace() (*httptrace.ClientTrace, *Debug) {
	d := &Debug{}

	t := &httptrace.ClientTrace{
		DNSStart: func(info httptrace.DNSStartInfo) {
			t := time.Now().UTC().String()
			log.Println(t, "dns start")
			d.DNS.Start = t
			d.DNS.Host = info.Host
		},
		DNSDone: func(info httptrace.DNSDoneInfo) {
			t := time.Now().UTC().String()
			log.Println(t, "dns end")
			d.DNS.End = t
			d.DNS.Address = info.Addrs
			d.DNS.Error = info.Err
		},
		ConnectStart: func(network, addr string) {
			t := time.Now().UTC().String()
			log.Println(t, "dial start")
			d.Dial.Start = t
		},
		ConnectDone: func(network, addr string, err error) {
			t := time.Now().UTC().String()
			log.Println(t, "dial end")
			d.Dial.End = t
		},
		GotConn: func(connInfo httptrace.GotConnInfo) {
			t := time.Now().UTC().String()
			log.Println(t, "conn time")
			d.Connection.Time = t
		},
		WroteHeaders: func() {
			t := time.Now().UTC().String()
			log.Println(t, "wrote all request headers")
			d.WroteAllRequestHeaders.Time = t
		},
		WroteRequest: func(wr httptrace.WroteRequestInfo) {
			t := time.Now().UTC().String()
			log.Println(t, "wrote all request")
			d.WroteAllRequest.Time = t
		},
		GotFirstResponseByte: func() {
			t := time.Now().UTC().String()
			log.Println(t, "first received response byte")
			d.FirstReceivedResponseByte.Time = t
		},
	}

	return t, d
}

测试

2020/03/26 18:11:28 2020-03-26 18:11:28.699923 +0000 UTC dns start
2020/03/26 18:11:28 2020-03-26 18:11:28.703246 +0000 UTC dns end
2020/03/26 18:11:28 2020-03-26 18:11:28.703556 +0000 UTC dial start
2020/03/26 18:11:28 2020-03-26 18:11:28.704488 +0000 UTC dial end
2020/03/26 18:11:28 2020-03-26 18:11:28.705182 +0000 UTC conn time
2020/03/26 18:11:28 2020-03-26 18:11:28.705841 +0000 UTC wrote all request headers
2020/03/26 18:11:28 2020-03-26 18:11:28.705920 +0000 UTC wrote all request
2020/03/26 18:11:28 2020-03-26 18:11:28.707156 +0000 UTC first received response byte

{
    "dns": {
        "start": "2020-03-26 18:11:28.699923 +0000 UTC",
        "end": "2020-03-26 18:11:28.703246 +0000 UTC",
        "host": "localhost",
        "address": [
            {
                "IP": "::1",
                "Zone": ""
            },
            {
                "IP": "127.0.0.1",
                "Zone": ""
            }
        ],
        "error": null
    },
    "dial": {
        "start": "2020-03-26 18:11:28.703556 +0000 UTC",
        "end": "2020-03-26 18:11:28.704488 +0000 UTC"
    },
    "connection": {
        "time": "2020-03-26 18:11:28.705182 +0000 UTC"
    },
    "wrote_all_request_header": {
        "time": "2020-03-26 18:11:28.705841 +0000 UTC"
    },
    "wrote_all_request": {
        "time": "2020-03-26 18:11:28.705920 +0000 UTC"
    },
    "first_received_response_byte": {
        "time": "2020-03-26 18:11:28.707156 +0000 UTC"
    }
}

real server responding: 2020-03-26 18:11:28.706893 +0000 UTC