-
Notifications
You must be signed in to change notification settings - Fork 2
/
tripper.go
135 lines (110 loc) · 3.06 KB
/
tripper.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
135
package gmeter
import (
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"log"
"net/http"
"net/http/httputil"
"sync"
"github.com/seborama/govcr"
)
var (
errNotInitialized = errors.New("gmeter is not initialized, please call /gmeter/record or /gmeter/play first")
)
type (
//RoundTripper implements http.RoundTripper instrumented with recording and playing capabilities
RoundTripper struct {
http.RoundTripper
lock sync.RWMutex
logger *log.Logger
options Options
}
request struct {
Cassette string `json:"cassette"`
}
nopTripper struct{}
)
//RoundTrip implements http.RoundTripper
func (nt nopTripper) RoundTrip(r *http.Request) (*http.Response, error) {
b, err := httputil.DumpRequest(r, true)
if err != nil {
return nil, fmt.Errorf("failed to dump request: %v", err)
}
return nil, fmt.Errorf("track not found for request: %s", string(b))
}
//NewRoundTripper returns a pointer to RoundTripper struct
func NewRoundTripper(options Options, logger *log.Logger) *RoundTripper {
return &RoundTripper{options: options, logger: logger}
}
//RoundTrip implements http.RoundTripper
func (rt *RoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
rt.lock.RLock()
defer rt.lock.RUnlock()
if rt.RoundTripper == nil {
return nil, errNotInitialized
}
resp, err := rt.RoundTripper.RoundTrip(r)
if resp != nil {
rt.logger.Printf("%s %s %d", r.Method, r.URL, resp.StatusCode)
}
return resp, err
}
//Record starts recording of a cassette
func (rt *RoundTripper) Record(w http.ResponseWriter, r *http.Request) {
rt.lock.Lock()
defer rt.lock.Unlock()
req, err := decodeRequest(r.Body)
if err != nil {
rt.logger.Printf("record failed: %v", err)
w.WriteHeader(http.StatusBadRequest)
return
}
config := govcr.VCRConfig{
DisableRecording: false,
CassettePath: rt.options.CassettePath,
Client: &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: rt.options.Insecure,
},
},
},
}
rt.RoundTripper = govcr.NewVCR(req.Cassette, &config).Client.Transport
rt.logger.Printf("started recording of the cassette: %s", req.Cassette)
}
//Play stops recording and starts playing a cassette
func (rt *RoundTripper) Play(w http.ResponseWriter, r *http.Request) {
rt.lock.Lock()
defer rt.lock.Unlock()
req, err := decodeRequest(r.Body)
if err != nil {
rt.logger.Printf("play failed: %v", err)
w.WriteHeader(http.StatusBadRequest)
return
}
config := govcr.VCRConfig{
DisableRecording: true,
CassettePath: rt.options.CassettePath,
Client: &http.Client{
Transport: nopTripper{},
},
}
rt.RoundTripper = govcr.NewVCR(req.Cassette, &config).Client.Transport
rt.logger.Printf("started playing the cassette: %s", req.Cassette)
}
var errEmptyCassette = errors.New("empty cassette name")
func decodeRequest(r io.Reader) (*request, error) {
var req request
decoder := json.NewDecoder(r)
if err := decoder.Decode(&req); err != nil {
return nil, fmt.Errorf("failed to decode request: %v", err)
}
if req.Cassette == "" {
return nil, errEmptyCassette
}
return &req, nil
}