github.com/oinume/lekcije@v0.0.0-20231017100347-5b4c5eb6ab24/backend/logger/http_transport.go (about) 1 package logger 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "net/http" 8 "net/http/httputil" 9 "strings" 10 "time" 11 12 "github.com/oinume/lekcije/backend/domain/config" 13 ) 14 15 type LoggingHTTPTransport struct { 16 DumpHeaderBody bool 17 } 18 19 func (t *LoggingHTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) { 20 var reqDump bytes.Buffer 21 chunked := false 22 if t.DumpHeaderBody { 23 if len(req.Header) > 0 { 24 fmt.Fprintln(&reqDump, "--- Request Header ---") 25 for k, v := range req.Header { 26 fmt.Fprintf(&reqDump, "%s: %s\n", k, strings.Join(v, ",")) 27 } 28 chunked = len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked" 29 if len(req.TransferEncoding) > 0 { 30 fmt.Fprintf(&reqDump, "Transfer-Encoding: %s\r\n", strings.Join(req.TransferEncoding, ",")) 31 } 32 if req.Close { 33 fmt.Fprint(&reqDump, "Connection: close\r\n") 34 } 35 } 36 if req.Body != nil { 37 fmt.Fprintln(&reqDump, "--- Request Body ---") 38 dump, _ := dumpRequestBody(req, chunked) 39 fmt.Fprint(&reqDump, string(dump)) 40 } 41 } 42 43 start := time.Now() 44 resp, err := http.DefaultTransport.RoundTrip(req) 45 if err != nil { 46 return nil, err 47 } 48 end := time.Now() 49 50 var out bytes.Buffer 51 fmt.Fprintf( 52 &out, "%s %s %d %d\n", 53 req.Method, req.URL, 54 resp.StatusCode, time.Duration(end.Sub(start).Nanoseconds())/time.Millisecond, 55 ) 56 57 var respDump bytes.Buffer 58 if t.DumpHeaderBody { 59 if len(resp.Header) > 0 { 60 fmt.Fprintln(&respDump, "--- Response Header ---") 61 for k, v := range resp.Header { 62 fmt.Fprintf(&respDump, "%s: %s\n", k, strings.Join(v, ",")) 63 } 64 } 65 save := resp.Body 66 savecl := resp.ContentLength 67 respBody := resp.Body 68 defer respBody.Close() 69 if resp.Body != nil { 70 save, resp.Body, _ = drainBody(resp.Body) 71 fmt.Fprintln(&respDump, "--- Response Body ---") 72 _, _ = io.Copy(&respDump, resp.Body) 73 } 74 resp.Body = save 75 resp.ContentLength = savecl 76 } 77 78 if s := reqDump.String(); s != "" { 79 fmt.Fprintln(&out, s) 80 } 81 if s := respDump.String(); s != "" { 82 fmt.Fprintln(&out, s) 83 } 84 85 if !config.DefaultVars.IsProductionEnv() { 86 fmt.Println(out.String()) 87 } 88 89 return resp, err 90 } 91 92 func (t *LoggingHTTPTransport) CancelRequest(r *http.Request) { 93 } 94 95 func dumpRequestBody(req *http.Request, chunked bool) ([]byte, error) { 96 // https://github.com/golang/go/blob/master/src/net/http/httputil/dump.go#L187 のDumpRequestを参考にしている 97 if req.Body == nil { 98 return []byte{}, nil 99 } 100 101 var err error 102 var save io.ReadCloser 103 save, req.Body, err = drainBody(req.Body) 104 if err != nil { 105 return nil, err 106 } 107 108 var b bytes.Buffer 109 var dest io.Writer = &b 110 if chunked { 111 dest = httputil.NewChunkedWriter(dest) 112 } 113 _, err = io.Copy(dest, req.Body) 114 if chunked { 115 _ = dest.(io.Closer).Close() 116 _, _ = io.WriteString(&b, "\r\n") 117 } 118 119 req.Body = save 120 if err != nil { 121 return nil, err 122 } 123 return b.Bytes(), nil 124 } 125 126 func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) { 127 var buf bytes.Buffer 128 if _, err = buf.ReadFrom(b); err != nil { 129 return nil, nil, err 130 } 131 if err = b.Close(); err != nil { 132 return nil, nil, err 133 } 134 return io.NopCloser(&buf), io.NopCloser(bytes.NewReader(buf.Bytes())), nil 135 }