Hi,
I am auditing http request and response by dumping both into a flat file which works at the moment. However, I have a feeling this can be improved because as far as I see, resource usage is high (RAM etc.).
Do you think there is better way or can we improve this code? Particularly interested in AUDIT REQUEST
and AUDIT RESPONSE
blocks.
Thanks
Bench results below reflects 100 concurrent users sending requests for 10 seconds.
Alloc = 12 MiB TotalAlloc = 534 MiB Sys = 27 MiB NumGC = 190
Alloc = 2 MiB TotalAlloc = 309 MiB Sys = 15 MiB NumGC = 148
Alloc = 4 MiB TotalAlloc = 348 MiB Sys = 11 MiB NumGC = 172
Alloc = 10 MiB TotalAlloc = 223 MiB Sys = 23 MiB NumGC = 39
Alloc = 3 MiB TotalAlloc = 167 MiB Sys = 11 MiB NumGC = 76
...
package xhttp
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"net/http/httputil"
"os"
"path/filepath"
"github.com/google/uuid"
)
type Client struct {
Client http.RoundTripper
}
func (c Client) Request(ctx context.Context, met, url string, bdy io.Reader, hdrs map[string]string) (*http.Response, error) {
req, err := http.NewRequestWithContext(ctx, met, url, bdy)
if err != nil {
return nil, err
}
for k, v := range hdrs {
req.Header.Add(k, v)
}
id := uuid.NewString()
// AUDIT REQUEST -----------------------------------------------------------
reqCopy := req.Clone(req.Context())
if req.Body != nil || req.Body != http.NoBody {
var buff bytes.Buffer
if _, err := io.Copy(&buff, req.Body); err == nil {
req.Body = io.NopCloser(bytes.NewReader(buff.Bytes()))
reqCopy.Body = io.NopCloser(bytes.NewReader(buff.Bytes()))
}
}
go LogRequest(reqCopy, id)
// -------------------------------------------------------------------------
res, err := c.Client.RoundTrip(req)
if err != nil {
return nil, err
}
// AUDIT RESPONSE ----------------------------------------------------------
resCopy := *res
if res.Body != nil || res.Body != http.NoBody {
var buff bytes.Buffer
if _, err := io.Copy(&buff, res.Body); err == nil {
res.Body = io.NopCloser(bytes.NewReader(buff.Bytes()))
resCopy.Body = io.NopCloser(bytes.NewReader(buff.Bytes()))
}
}
go LogResponse(&resCopy, req, id)
// -------------------------------------------------------------------------
return res, nil
}
func LogRequest(req *http.Request, id string) {
dump, err := httputil.DumpRequest(req, true)
if err != nil {
fmt.Println("dump request", err)
return
}
path := id + "_request.log"
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
fmt.Println("mkdir all:", err)
return
}
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(0600))
if err != nil {
fmt.Println("open file:", err)
return
}
defer file.Close()
if _, err := file.Write(dump); err != nil {
fmt.Println("file write:", err)
return
}
}
func LogResponse(res *http.Response, req *http.Request, id string) {
dump, err := httputil.DumpResponse(res, true)
if err != nil {
fmt.Println("dump response", err)
return
}
defer res.Body.Close()
method := http.MethodGet
if req.Method != "" {
method = req.Method
}
uri := req.RequestURI
if uri == "" {
uri = req.URL.RequestURI()
}
dump = append(
[]byte(fmt.Sprintf("%s %s HTTP/%d.%d\nHost: %s\n", method, uri, req.ProtoMajor, req.ProtoMinor, req.URL.Host)),
dump...,
)
path := id + "_response.log"
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
fmt.Println("mkdir all:", err)
return
}
file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.FileMode(0600))
if err != nil {
fmt.Println("open file:", err)
return
}
defer file.Close()
if _, err := file.Write(dump); err != nil {
fmt.Println("file write:", err)
return
}
}