-
Notifications
You must be signed in to change notification settings - Fork 0
/
betwixt.go
121 lines (103 loc) · 2.66 KB
/
betwixt.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
package betwixt
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"sync"
"github.com/SimonRichardson/betwixt/pkg/entry"
)
// Betwixt is a struct that holds all the entries and outputs to be processed
type Betwixt struct {
mutex sync.Mutex
entries []entry.Entry
outputs []Output
handler http.Handler
}
// New creates a Betwixt for possible outputs
func New(handler http.Handler, outputs []Output) *Betwixt {
return &Betwixt{
mutex: sync.Mutex{},
outputs: outputs,
handler: handler,
}
}
// ServeHTTP handles all the middleware for creating the documents
func (b *Betwixt) ServeHTTP(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
bodyBytes, err := ioutil.ReadAll(r.Body)
if err != nil {
panic(err)
}
r.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
writer := httptest.NewRecorder()
b.handler.ServeHTTP(writer, r)
writer.Flush()
nativeHeaders := w.Header()
for k, v := range writer.Header() {
for _, v := range v {
nativeHeaders.Add(k, v)
}
}
w.WriteHeader(writer.Code)
w.Write(writer.Body.Bytes())
// Only handle successful status codes
if status := writer.Code; status >= 200 && status < 300 {
b.mutex.Lock()
defer b.mutex.Unlock()
b.entries = append(b.entries, entry.Entry{
URL: r.URL,
Method: r.Method,
Status: writer.Code,
ReqHeaders: r.Header,
ReqBody: func() []byte {
return bodyBytes
},
RespHeaders: writer.Header(),
RespBody: func() []byte {
return writer.Body.Bytes()
},
})
}
}
// Output the results
func (b *Betwixt) Output() error {
b.mutex.Lock()
defer b.mutex.Unlock()
grouped, err := group(b.entries)
if err != nil {
return err
}
for _, v := range b.outputs {
if err := v.Output(grouped); err != nil {
return err
}
}
return nil
}
// Output defines an interface for consuming a document.
type Output interface {
Output([]entry.Document) error
}
func group(entries []entry.Entry) ([]entry.Document, error) {
// Group according to the url and status code.
groups := entry.Entries(entries).GroupBy(func(entry entry.Entry) string {
url := fmt.Sprintf("%s/%s", entry.URL.Host, entry.NormalisePath())
return fmt.Sprintf("%s-%s-%d", entry.Method, url, entry.Status)
})
// Loop through all the groups and find differences.
return groups.Walk(func(entries entry.Entries) (entry.Document, error) {
url := entries.URL()
return entry.Document{
URL: url,
Method: entries.Method(),
Status: entries.Status(),
Params: entries.Params(),
ReqHeaders: entries.ReqHeaders(),
ReqBody: entries.ReqBody(),
RespHeaders: entries.RespHeaders(),
RespBody: entries.RespBody(),
}, nil
})
}