Goで外部通信の中身を確認する
使用しているライブラリの外部通信の中身(リクエストのヘッダやレスポンスのステータス等)をデバッグのために出力したくなったのでまとめました。
方法
以下のように外部のサーバーにリクエストするようなコードがあったとします。
package main
import (
"context"
"fmt"
"net/http"
)
func main() {
if err := run(); err != nil {
panic(err)
}
}
var c = &http.Client{}
func run() error {
ctx := context.Background()
req, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
"http://localhost:8080",
nil,
)
if err != nil {
return err
}
res, err := c.Do(req)
if err != nil {
return err
}
fmt.Println(res.Status)
return nil
}この時、外部との通信の中身を確認したい場合、以下のようなコードを追加すると出力できます。
func init() {
http.DefaultTransport = &myRoundTripper{
RoundTripper: http.DefaultTransport,
}
}
type myRoundTripper struct {
http.RoundTripper
}
func (r *myRoundTripper) RoundTrip(req *http.Request) (
*http.Response, error) {
fmt.Println("req.Header:", req.Header)
res, err := r.RoundTripper.RoundTrip(req)
fmt.Println("res.Status:", res.Status)
return res, err
}
$ go run main.go
req.Header: map[]
res.Status: 200 OKコードのどこでも良いので、 init() で http.DefaultTransport を上書きすればOKです。
解説
http.Client はリクエストを送信する時、内部で Client.Transport を使用しますが、設定されていなければ http.DefaultTransport を使うよになっています。
https://cs.opensource.google/go/go/+/master:src/net/http/client.go;l=170
func (c *Client) send(req *Request, deadline time.Time) (
resp *Response, didTimeout func() bool, err error) {
...
resp, didTimeout, err = send(req, c.transport(), deadline)
...
}
...
func (c *Client) transport() RoundTripper {
if c.Transport != nil {
return c.Transport
}
return DefaultTransport
}この RoundTripper は以下の定義のinterfaceです。
https://cs.opensource.google/go/go/+/master:src/net/http/client.go;l=117
type RoundTripper interface {
RoundTrip(*Request) (*Response, error)
}そのため、このinterfaceを満たす構造体を定義し、http.DefaultTransport を上書きすると、通信の前後に任意の処理を挟むことができます。
func init() {
http.DefaultTransport = &myRoundTripper{
RoundTripper: http.DefaultTransport,
}
}
type myRoundTripper struct {
http.RoundTripper
}
func (r *myRoundTripper) RoundTrip(req *http.Request) (
*http.Response, error) {
// reqを出力
// 実際の通信は元々の http.DefaultTransport に任せる
res, err := r.RoundTripper.RoundTrip(req)
// resを出力
return res, err
}
この処理は http.DefaultTransport を使う全ての通信に適用されます。例えば、 github.com/aws/aws-sdk-go はデフォルトで http.DefaultClient を使うので、以下のコードで通信を出力できます。
package main
import (
"bytes"
"fmt"
"io"
"net/http"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/dynamodb"
)
func init() {
http.DefaultTransport = &myRoundTripper{
RoundTripper: http.DefaultTransport,
}
}
type myRoundTripper struct {
http.RoundTripper
}
func (r *myRoundTripper) RoundTrip(req *http.Request) (
*http.Response, error) {
fmt.Println("req.URL:", req.URL.String())
if req.Body != nil {
reqBody, _ := io.ReadAll(req.Body)
req.Body.Close()
req.Body = io.NopCloser(bytes.NewBuffer(reqBody))
fmt.Println("req.Body:", string(reqBody))
}
res, err := r.RoundTripper.RoundTrip(req)
fmt.Println("res.Status:", res.Status)
if res.Body != nil {
resBody, _ := io.ReadAll(res.Body)
res.Body.Close()
res.Body = io.NopCloser(bytes.NewBuffer(resBody))
fmt.Println("res.Body:", string(resBody))
}
return res, err
}
func main() {
if err := run(); err != nil {
panic(err)
}
}
func run() error {
sess := session.Must(session.NewSession(&aws.Config{
Region: aws.String("ap-northeast-1"),
}))
client := dynamodb.New(sess)
res, err := client.ListTables(&dynamodb.ListTablesInput{})
if err != nil {
return err
}
fmt.Println(res)
return nil
}
$ go run main.go
req.URL: https://dynamodb.ap-northeast-1.amazonaws.com/
req.Body: {}
res.Status: 200 OK
res.Body: {"TableNames":...}ちなみに github.com/aws/aws-sdk-go-v2 は *http.Transport を自前で生成しているので、同じようにしてもログは出ません。
func run() error {
ctx := context.Background()
cfg, err := config.LoadDefaultConfig(
ctx,
config.WithRegion("ap-northeast-1"),
)
if err != nil {
return err
}
client := v2dynamodb.NewFromConfig(cfg)
res, err := client.ListTables(ctx, &v2dynamodb.ListTablesInput{})
if err != nil {
return err
}
fmt.Println(res)
return nil
}使用する http.Client を指定するとログが出るようになります。
func run() error {
ctx := context.Background()
cfg, err := config.LoadDefaultConfig(
ctx,
config.WithRegion("ap-northeast-1"),
// http.DefaultClient を設定
config.WithHTTPClient(http.DefaultClient),
)
...$ go run main.go
req.URL: https://dynamodb.ap-northeast-1.amazonaws.com/
req.Body: {}
res.Status: 200 OK
res.Body: {"TableNames":...}大抵のライブラリはこのようにクライアントを設定する方法があると思うので、同じように出力できます。