Example #1
0
// Proxies client requests to their original destination
func requestHandler(r *http.Request, c *cassette.Cassette, mode RecorderMode) (
	*cassette.Interaction, error) {

	// Return interaction from cassette if in replay mode
	if mode == ModeReplaying {
		return c.GetInteraction(r)
	}

	c.RequestStated(r.URL.String())

	// Copy the original request, so we can read the form values
	reqBytes, err := httputil.DumpRequestOut(r, true)
	if err != nil {
		return nil, err
	}

	reqBuffer := bytes.NewBuffer(reqBytes)
	copiedReq, err := http.ReadRequest(bufio.NewReader(reqBuffer))
	if err != nil {
		return nil, err
	}

	err = copiedReq.ParseForm()
	if err != nil {
		return nil, err
	}

	// Perform client request to it's original
	// destination and record interactions
	body := ioutil.NopCloser(r.Body)
	req, err := http.NewRequest(r.Method, r.URL.String(), body)
	if err != nil {
		return nil, err
	}

	req.Header = r.Header
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}

	// Record the interaction and add it to the cassette
	reqBody, err := ioutil.ReadAll(req.Body)
	if err != nil {
		return nil, err
	}

	respBody, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	c.RequestFinished(r.URL.String())

	// Add interaction to cassette
	interaction := &cassette.Interaction{
		Request: cassette.Request{
			Body:    string(reqBody),
			Form:    copiedReq.PostForm,
			Headers: req.Header,
			URL:     req.URL.String(),
			Method:  req.Method,
		},
		Response: cassette.Response{
			Body:    string(respBody),
			Headers: resp.Header,
			Status:  resp.Status,
			Code:    resp.StatusCode,
		},
	}
	c.AddInteraction(interaction)

	return interaction, nil
}
Example #2
0
// 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
}