// Creates a new recorder func New(cassetteName string) (*Recorder, error) { var mode RecorderMode var c *cassette.Cassette var wg sync.WaitGroup cassetteFile := fmt.Sprintf("%s.yaml", cassetteName) // Depending on whether the cassette file exists or not we // either create a new empty cassette or load from file if _, err := os.Stat(cassetteFile); os.IsNotExist(err) { // Create new cassette and enter in recording mode c = cassette.New(cassetteName) mode = ModeRecording } else { // Load cassette from file and enter replay mode c, err = cassette.Load(cassetteName) if err != nil { return nil, err } mode = ModeReplaying wg.Add(len(c.UnclosedRequests)) } rec := &Recorder{ mode: mode, cassette: c, wg: &wg, } doneRequests := make(map[string]struct{}) var doneRequestMu sync.RWMutex // Handler for client requests handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if rec.mode == ModeReplaying && c.HasRequest(r.URL.String()) { doneRequestMu.RLock() _, duplicate := doneRequests[r.URL.String()] doneRequestMu.RUnlock() if duplicate != true { doneRequestMu.Lock() doneRequests[r.URL.String()] = struct{}{} doneRequestMu.Unlock() } cn, ok := w.(http.CloseNotifier) if !ok { log.Fatal("don't support CloseNotifier") } <-cn.CloseNotify() if duplicate != true { wg.Done() } return } interaction, err := requestHandler(r, c, mode) if err != nil { panic(fmt.Errorf("Failed to process request for URL:\n%s\n%s", r.URL, err)) } w.WriteHeader(interaction.Response.Code) body := strings.TrimSuffix(interaction.Response.Body, "\n") fmt.Fprintln(w, body) }) // HTTP server used to mock requests rec.server = httptest.NewServer(handler) // A proxy function which routes all requests through our HTTP server // Can be used by clients to inject into their own transports proxyUrl, err := url.Parse(rec.server.URL) if err != nil { return nil, err } // A transport which can be used by clients to inject rec.Transport = &http.Transport{ Proxy: http.ProxyURL(proxyUrl), } return rec, nil }