{
    "componentChunkName": "component---src-templates-post-js",
    "path": "/go-http/",
    "result": {"data":{"ghostPost":{"id":"Ghost__Post__63b2363aa35ab30001cac870","title":"Goで外部通信の中身を確認する","slug":"go-http","featured":false,"feature_image":null,"excerpt":"使用しているライブラリの外部通信の中身（リクエストのヘッダやレスポンスのステータス等）をデバッグのために出力したくなったのでまとめました。\n\n方法\n以下のように外部のサーバーにリクエストするようなコードがあったとします。\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n)\n\nfunc main() {\n\tif err := run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nvar c = &http.Client{}\n\nfunc run() error {\n\tctx := context.Background()\n\treq, err := http.NewRequestWithContext(\n\t\tctx,\n\t\thttp.MethodGet,\n\t\t\"http://localhost:8080\",\n\t\tnil,\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\tres, err := c.Do(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt","custom_excerpt":null,"visibility":"public","created_at_pretty":"02 January, 2023","published_at_pretty":"19 January, 2023","updated_at_pretty":"19 January, 2023","created_at":"2023-01-02T10:41:14.000+09:00","published_at":"2023-01-20T08:01:30.000+09:00","updated_at":"2023-01-20T08:01:30.000+09:00","meta_title":null,"meta_description":null,"og_description":null,"og_image":null,"og_title":null,"twitter_description":null,"twitter_image":null,"twitter_title":null,"authors":[{"name":"Yu Takahashi","slug":"yu","bio":null,"profile_image":null,"twitter":null,"facebook":null,"website":null}],"primary_author":{"name":"Yu Takahashi","slug":"yu","bio":null,"profile_image":null,"twitter":null,"facebook":null,"website":null},"primary_tag":{"name":"Go","slug":"go","description":null,"feature_image":null,"meta_description":null,"meta_title":null,"visibility":"public"},"tags":[{"name":"Go","slug":"go","description":null,"feature_image":null,"meta_description":null,"meta_title":null,"visibility":"public"}],"plaintext":"使用しているライブラリの外部通信の中身（リクエストのヘッダやレスポンスのステータス等）をデバッグのために出力したくなったのでまとめました。\n\n方法\n以下のように外部のサーバーにリクエストするようなコードがあったとします。\n\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n)\n\nfunc main() {\n\tif err := run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nvar c = &http.Client{}\n\nfunc run() error {\n\tctx := context.Background()\n\treq, err := http.NewRequestWithContext(\n\t\tctx,\n\t\thttp.MethodGet,\n\t\t\"http://localhost:8080\",\n\t\tnil,\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\tres, err := c.Do(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Println(res.Status)\n\treturn nil\n}\n\nこの時、外部との通信の中身を確認したい場合、以下のようなコードを追加すると出力できます。\n\nfunc init() {\n\thttp.DefaultTransport = &myRoundTripper{\n\t\tRoundTripper: http.DefaultTransport,\n\t}\n}\n\ntype myRoundTripper struct {\n\thttp.RoundTripper\n}\n\nfunc (r *myRoundTripper) RoundTrip(req *http.Request) (\n\t*http.Response, error) {\n\tfmt.Println(\"req.Header:\", req.Header)\n\n\tres, err := r.RoundTripper.RoundTrip(req)\n\n\tfmt.Println(\"res.Status:\", res.Status)\n\treturn res, err\n}\n\n\n$ go run main.go\nreq.Header: map[]\nres.Status: 200 OK\n\nコードのどこでも良いので、 init() で http.DefaultTransport を上書きすればOKです。\n\n解説\nhttp.Client はリクエストを送信する時、内部で Client.Transport を使用しますが、設定されていなければ \nhttp.DefaultTransport を使うよになっています。\n\nhttps://cs.opensource.google/go/go/+/master:src/net/http/client.go;l=170\n\nfunc (c *Client) send(req *Request, deadline time.Time) (\n\tresp *Response, didTimeout func() bool, err error) {\n\t...\n\tresp, didTimeout, err = send(req, c.transport(), deadline)\n\t...\n}\n\n...\n\nfunc (c *Client) transport() RoundTripper {\n\tif c.Transport != nil {\n\t\treturn c.Transport\n\t}\n\treturn DefaultTransport\n}\n\nこの RoundTripper は以下の定義のinterfaceです。\n\nhttps://cs.opensource.google/go/go/+/master:src/net/http/client.go;l=117\n\ntype RoundTripper interface {\n\tRoundTrip(*Request) (*Response, error)\n}\n\nそのため、このinterfaceを満たす構造体を定義し、http.DefaultTransport を上書きすると、通信の前後に任意の処理を挟むことができます。\n\nfunc init() {\n\thttp.DefaultTransport = &myRoundTripper{\n\t\tRoundTripper: http.DefaultTransport,\n\t}\n}\n\ntype myRoundTripper struct {\n\thttp.RoundTripper\n}\n\nfunc (r *myRoundTripper) RoundTrip(req *http.Request) (\n\t*http.Response, error) {\n\n\t// reqを出力\n\n\t// 実際の通信は元々の http.DefaultTransport に任せる\n\tres, err := r.RoundTripper.RoundTrip(req)\n\n\t// resを出力\n\n\treturn res, err\n}\n\n\nこの処理は http.DefaultTransport を使う全ての通信に適用されます。例えば、 github.com/aws/aws-sdk-go\n[https://github.com/aws/aws-sdk-go] はデフォルトで\n[https://github.com/aws/aws-sdk-go/blob/e2d6cb448883e4f4fcc5246650f89bde349041ec/aws/defaults/defaults.go#L59]\nhttp.DefaultClient\n[https://github.com/aws/aws-sdk-go/blob/e2d6cb448883e4f4fcc5246650f89bde349041ec/aws/defaults/defaults.go#L59] \nを使う\n[https://github.com/aws/aws-sdk-go/blob/e2d6cb448883e4f4fcc5246650f89bde349041ec/aws/defaults/defaults.go#L59]\nので、以下のコードで通信を出力できます。\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/aws/session\"\n\t\"github.com/aws/aws-sdk-go/service/dynamodb\"\n)\n\nfunc init() {\n\thttp.DefaultTransport = &myRoundTripper{\n\t\tRoundTripper: http.DefaultTransport,\n\t}\n}\n\ntype myRoundTripper struct {\n\thttp.RoundTripper\n}\n\nfunc (r *myRoundTripper) RoundTrip(req *http.Request) (\n\t*http.Response, error) {\n\tfmt.Println(\"req.URL:\", req.URL.String())\n\tif req.Body != nil {\n\t\treqBody, _ := io.ReadAll(req.Body)\n\t\treq.Body.Close()\n\t\treq.Body = io.NopCloser(bytes.NewBuffer(reqBody))\n\t\tfmt.Println(\"req.Body:\", string(reqBody))\n\t}\n\n\tres, err := r.RoundTripper.RoundTrip(req)\n\n\tfmt.Println(\"res.Status:\", res.Status)\n\tif res.Body != nil {\n\t\tresBody, _ := io.ReadAll(res.Body)\n\t\tres.Body.Close()\n\t\tres.Body = io.NopCloser(bytes.NewBuffer(resBody))\n\t\tfmt.Println(\"res.Body:\", string(resBody))\n\t}\n\treturn res, err\n}\n\nfunc main() {\n\tif err := run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc run() error {\n\tsess := session.Must(session.NewSession(&aws.Config{\n\t\tRegion: aws.String(\"ap-northeast-1\"),\n\t}))\n\tclient := dynamodb.New(sess)\n\tres, err := client.ListTables(&dynamodb.ListTablesInput{})\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Println(res)\n\n\treturn nil\n}\n\n\n$ go run main.go\nreq.URL: https://dynamodb.ap-northeast-1.amazonaws.com/\nreq.Body: {}\nres.Status: 200 OK\nres.Body: {\"TableNames\":...}\n\nちなみに github.com/aws/aws-sdk-go-v2 [https://github.com/aws/aws-sdk-go-v2] は \n*http.Transport を自前で生成している\n[https://github.com/aws/aws-sdk-go-v2/blob/06ce35b2b871e70352861a04a903e1045c923f66/aws/transport/http/client.go#L177]\nので、同じようにしてもログは出ません。\n\nfunc run() error {\n\tctx := context.Background()\n\tcfg, err := config.LoadDefaultConfig(\n\t\tctx,\n\t\tconfig.WithRegion(\"ap-northeast-1\"),\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\tclient := v2dynamodb.NewFromConfig(cfg)\n\tres, err := client.ListTables(ctx, &v2dynamodb.ListTablesInput{})\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Println(res)\n\n\treturn nil\n}\n\n使用する http.Client を指定するとログが出るようになります。\n\nfunc run() error {\n\tctx := context.Background()\n\tcfg, err := config.LoadDefaultConfig(\n\t\tctx,\n\t\tconfig.WithRegion(\"ap-northeast-1\"),\n\t\t// http.DefaultClient を設定\n\t\tconfig.WithHTTPClient(http.DefaultClient),\n\t)\n\t...\n\n$ go run main.go\nreq.URL: https://dynamodb.ap-northeast-1.amazonaws.com/\nreq.Body: {}\nres.Status: 200 OK\nres.Body: {\"TableNames\":...}\n\n大抵のライブラリはこのようにクライアントを設定する方法があると思うので、同じように出力できます。","html":"<p>使用しているライブラリの外部通信の中身（リクエストのヘッダやレスポンスのステータス等）をデバッグのために出力したくなったのでまとめました。</p><h3 id=\"%E6%96%B9%E6%B3%95\">方法</h3><p>以下のように外部のサーバーにリクエストするようなコードがあったとします。</p><pre><code>package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"net/http\"\n)\n\nfunc main() {\n\tif err := run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nvar c = &amp;http.Client{}\n\nfunc run() error {\n\tctx := context.Background()\n\treq, err := http.NewRequestWithContext(\n\t\tctx,\n\t\thttp.MethodGet,\n\t\t\"http://localhost:8080\",\n\t\tnil,\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\tres, err := c.Do(req)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Println(res.Status)\n\treturn nil\n}</code></pre><p>この時、外部との通信の中身を確認したい場合、以下のようなコードを追加すると出力できます。</p><pre><code>func init() {\n\thttp.DefaultTransport = &amp;myRoundTripper{\n\t\tRoundTripper: http.DefaultTransport,\n\t}\n}\n\ntype myRoundTripper struct {\n\thttp.RoundTripper\n}\n\nfunc (r *myRoundTripper) RoundTrip(req *http.Request) (\n\t*http.Response, error) {\n\tfmt.Println(\"req.Header:\", req.Header)\n\n\tres, err := r.RoundTripper.RoundTrip(req)\n\n\tfmt.Println(\"res.Status:\", res.Status)\n\treturn res, err\n}\n</code></pre><pre><code>$ go run main.go\nreq.Header: map[]\nres.Status: 200 OK</code></pre><p>コードのどこでも良いので、 <code>init()</code> で <code>http.DefaultTransport</code> を上書きすればOKです。</p><h3 id=\"%E8%A7%A3%E8%AA%AC\">解説</h3><p><code>http.Client</code> はリクエストを送信する時、内部で <code>Client.Transport</code> を使用しますが、設定されていなければ <code>http.DefaultTransport</code> を使うよになっています。</p><p><a href=\"https://cs.opensource.google/go/go/+/master:src/net/http/client.go;l=170\">https://cs.opensource.google/go/go/+/master:src/net/http/client.go;l=170</a></p><pre><code>func (c *Client) send(req *Request, deadline time.Time) (\n\tresp *Response, didTimeout func() bool, err error) {\n\t...\n\tresp, didTimeout, err = send(req, c.transport(), deadline)\n\t...\n}\n\n...\n\nfunc (c *Client) transport() RoundTripper {\n\tif c.Transport != nil {\n\t\treturn c.Transport\n\t}\n\treturn DefaultTransport\n}</code></pre><p>この <code>RoundTripper</code> は以下の定義のinterfaceです。</p><p><a href=\"https://cs.opensource.google/go/go/+/master:src/net/http/client.go;l=117\">https://cs.opensource.google/go/go/+/master:src/net/http/client.go;l=117</a></p><pre><code>type RoundTripper interface {\n\tRoundTrip(*Request) (*Response, error)\n}</code></pre><p>そのため、このinterfaceを満たす構造体を定義し、<code>http.DefaultTransport</code> を上書きすると、通信の前後に任意の処理を挟むことができます。</p><pre><code>func init() {\n\thttp.DefaultTransport = &amp;myRoundTripper{\n\t\tRoundTripper: http.DefaultTransport,\n\t}\n}\n\ntype myRoundTripper struct {\n\thttp.RoundTripper\n}\n\nfunc (r *myRoundTripper) RoundTrip(req *http.Request) (\n\t*http.Response, error) {\n\n\t// reqを出力\n\n\t// 実際の通信は元々の http.DefaultTransport に任せる\n\tres, err := r.RoundTripper.RoundTrip(req)\n\n\t// resを出力\n\n\treturn res, err\n}\n</code></pre><p>この処理は <code>http.DefaultTransport</code> を使う全ての通信に適用されます。例えば、 <code><a href=\"https://github.com/aws/aws-sdk-go\">github.com/aws/aws-sdk-go</a></code> は<a href=\"https://github.com/aws/aws-sdk-go/blob/e2d6cb448883e4f4fcc5246650f89bde349041ec/aws/defaults/defaults.go#L59\">デフォルトで </a><code><a href=\"https://github.com/aws/aws-sdk-go/blob/e2d6cb448883e4f4fcc5246650f89bde349041ec/aws/defaults/defaults.go#L59\">http.DefaultClient</a></code><a href=\"https://github.com/aws/aws-sdk-go/blob/e2d6cb448883e4f4fcc5246650f89bde349041ec/aws/defaults/defaults.go#L59\"> を使う</a>ので、以下のコードで通信を出力できます。</p><pre><code>package main\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\n\t\"github.com/aws/aws-sdk-go/aws\"\n\t\"github.com/aws/aws-sdk-go/aws/session\"\n\t\"github.com/aws/aws-sdk-go/service/dynamodb\"\n)\n\nfunc init() {\n\thttp.DefaultTransport = &amp;myRoundTripper{\n\t\tRoundTripper: http.DefaultTransport,\n\t}\n}\n\ntype myRoundTripper struct {\n\thttp.RoundTripper\n}\n\nfunc (r *myRoundTripper) RoundTrip(req *http.Request) (\n\t*http.Response, error) {\n\tfmt.Println(\"req.URL:\", req.URL.String())\n\tif req.Body != nil {\n\t\treqBody, _ := io.ReadAll(req.Body)\n\t\treq.Body.Close()\n\t\treq.Body = io.NopCloser(bytes.NewBuffer(reqBody))\n\t\tfmt.Println(\"req.Body:\", string(reqBody))\n\t}\n\n\tres, err := r.RoundTripper.RoundTrip(req)\n\n\tfmt.Println(\"res.Status:\", res.Status)\n\tif res.Body != nil {\n\t\tresBody, _ := io.ReadAll(res.Body)\n\t\tres.Body.Close()\n\t\tres.Body = io.NopCloser(bytes.NewBuffer(resBody))\n\t\tfmt.Println(\"res.Body:\", string(resBody))\n\t}\n\treturn res, err\n}\n\nfunc main() {\n\tif err := run(); err != nil {\n\t\tpanic(err)\n\t}\n}\n\nfunc run() error {\n\tsess := session.Must(session.NewSession(&amp;aws.Config{\n\t\tRegion: aws.String(\"ap-northeast-1\"),\n\t}))\n\tclient := dynamodb.New(sess)\n\tres, err := client.ListTables(&amp;dynamodb.ListTablesInput{})\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Println(res)\n\n\treturn nil\n}\n</code></pre><pre><code>$ go run main.go\nreq.URL: https://dynamodb.ap-northeast-1.amazonaws.com/\nreq.Body: {}\nres.Status: 200 OK\nres.Body: {\"TableNames\":...}</code></pre><p>ちなみに <code><a href=\"https://github.com/aws/aws-sdk-go-v2\">github.com/aws/aws-sdk-go-v2</a></code> は <a href=\"https://github.com/aws/aws-sdk-go-v2/blob/06ce35b2b871e70352861a04a903e1045c923f66/aws/transport/http/client.go#L177\"><code>*http.Transport</code> を自前で生成している</a>ので、同じようにしてもログは出ません。</p><pre><code>func run() error {\n\tctx := context.Background()\n\tcfg, err := config.LoadDefaultConfig(\n\t\tctx,\n\t\tconfig.WithRegion(\"ap-northeast-1\"),\n\t)\n\tif err != nil {\n\t\treturn err\n\t}\n\tclient := v2dynamodb.NewFromConfig(cfg)\n\tres, err := client.ListTables(ctx, &amp;v2dynamodb.ListTablesInput{})\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Println(res)\n\n\treturn nil\n}</code></pre><p>使用する <code>http.Client</code> を指定するとログが出るようになります。</p><pre><code>func run() error {\n\tctx := context.Background()\n\tcfg, err := config.LoadDefaultConfig(\n\t\tctx,\n\t\tconfig.WithRegion(\"ap-northeast-1\"),\n\t\t// http.DefaultClient を設定\n\t\tconfig.WithHTTPClient(http.DefaultClient),\n\t)\n\t...</code></pre><pre><code>$ go run main.go\nreq.URL: https://dynamodb.ap-northeast-1.amazonaws.com/\nreq.Body: {}\nres.Status: 200 OK\nres.Body: {\"TableNames\":...}</code></pre><p>大抵のライブラリはこのようにクライアントを設定する方法があると思うので、同じように出力できます。</p>","url":"https://ghost.tech.anti-pattern.co.jp/go-http/","canonical_url":null,"uuid":"e56f94e9-1644-463f-9aab-c80a8d67b6f9","page":null,"codeinjection_foot":null,"codeinjection_head":null,"codeinjection_styles":null,"comment_id":"63b2363aa35ab30001cac870","reading_time":3}},"pageContext":{"slug":"go-http"}},
    "staticQueryHashes": ["176528973","2358152166","2561578252","2731221146","4145280475"]}