forked from abourget/goproxy
/
harlog.go
134 lines (113 loc) · 3.31 KB
/
harlog.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package goproxy
import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"os"
"time"
"github.com/abourget/goproxy/har"
)
func (proxy *ProxyHttpServer) harLogAggregator() {
proxy.Logf("Launching harLogAggregator()")
for {
select {
case reqAndResp := <-proxy.harLogEntryCh:
harEntry := new(har.Entry)
harEntry.Request = har.ParseRequest(reqAndResp.req, reqAndResp.captureContent)
harEntry.StartedDateTime = reqAndResp.start
harEntry.Response = har.ParseResponse(reqAndResp.resp, reqAndResp.captureContent)
harEntry.Time = reqAndResp.end.Sub(reqAndResp.start).Nanoseconds() / 1e6
harEntry.FillIPAddress(reqAndResp.req) // should take it from the actual conn?
if len(proxy.harLog.Log.Entries) == 0 {
proxy.harLog.AppendPage(har.Page{
ID: "0",
StartedDateTime: harEntry.StartedDateTime,
Title: "GoProxy Log",
})
}
harEntry.PageRef = "0"
proxy.harLog.AppendEntry(*harEntry)
case filename := <-proxy.harFlushRequest:
proxy.Logf("Received HAR flush request to %q", filename)
if len(proxy.harLog.Log.Entries) == 0 {
proxy.Logf("No HAR entries to flush")
continue
}
err := flushHarToDisk(proxy.harLog, filename)
if err != nil {
proxy.Logf("Error flushing HAR file to disk: %s", err)
} else {
proxy.Logf("Wrote HAR file to disk: %s", filename)
}
proxy.harLog = har.New() // reset
}
}
}
func flushHarToDisk(har *har.Har, filename string) error {
file, err := os.Create(filename)
if err != nil {
return err
}
defer file.Close()
jsonHar, err := json.MarshalIndent(har, "", " ")
if err != nil {
return err
}
_, err = file.Write(jsonHar)
if err != nil {
return err
}
return nil
}
type harReqAndResp struct {
req *http.Request
start time.Time
resp *http.Response
end time.Time
captureContent bool
}
func copyReq(req *http.Request) (*http.Request, *http.Request) {
reqCopy := new(http.Request)
*reqCopy = *req
req.Body, reqCopy.Body = copyReadCloser(req.Body, req.ContentLength)
return req, reqCopy
}
func copyResp(resp *http.Response) (*http.Response, *http.Response) {
respCopy := new(http.Response)
*respCopy = *resp
resp.Body, respCopy.Body = copyReadCloser(resp.Body, resp.ContentLength)
return resp, respCopy
}
func copyReadCloser(readCloser io.ReadCloser, len int64) (io.ReadCloser, io.ReadCloser) {
if len < 0 {
len = 0
}
temp := bytes.NewBuffer(make([]byte, 0, len))
teeReader := io.TeeReader(readCloser, temp)
copy := bytes.NewBuffer(make([]byte, 0, len))
copy.ReadFrom(teeReader)
return ioutil.NopCloser(temp), ioutil.NopCloser(copy)
}
// LogToHARFile collects all the content from the Request/Response
// roundtrip and stores it in memory until you call
// `FlushHARToDisk(filename)`.. at which point it will all be flushed
// to disk in HAR file format.
//
// LogToHARFile alwasy returns `NEXT`.
func (ctx *ProxyCtx) LogToHARFile(captureContent bool) Next {
ctx.proxy.harFlusherRunOnce.Do(func() {
go ctx.proxy.harLogAggregator()
})
ctx.isLogEnabled = true
ctx.isLogWithContent = captureContent
return NEXT
}
func (ctx *ProxyCtx) FlushHARToDisk(filename string) {
ctx.proxy.FlushHARToDisk(filename)
}
func (proxy *ProxyHttpServer) FlushHARToDisk(filename string) {
proxy.Logf("Calling a flush of HAR to disk")
proxy.harFlushRequest <- filename
}