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  }