func (s *Splitter) handleRequest(ctx context.Context, reqresp *httputils.RequestResponse) { log.Println("got request to split:", reqresp) if s.Config.WaitForResponse { log.Println("waiting for response") select { case <-ctx.Done(): return case <-reqresp.Done(): } log.Println("done waiting for response") } sinkResponses := make(chan *httputils.RequestResponse, len(s.Config.Sinks)) responsesDone := make(chan struct{}) wg := sync.WaitGroup{} ctx, _ = context.WithTimeout(ctx, time.Second*time.Duration(s.Config.SinkRequestTimeout)) for _, sink := range s.Config.Sinks { wg.Add(1) req, err := httputils.CopyRequest(reqresp.Request) if err != nil { log.Println("failed to copy request:", err) } go func(sink config.Sink, req *http.Request) { defer wg.Done() sinkResponse, err := s.performSinkRequest(ctx, req, sink) if err != nil { log.Println("error performing request to", sink, "-", err) return } sinkResponses <- sinkResponse }(sink, req) } go func() { wg.Wait() close(responsesDone) close(sinkResponses) }() responses := []*httputils.RequestResponse{} select { case <-ctx.Done(): log.Println("handleRequest: got cancellation signal") case <-responsesDone: } for response := range sinkResponses { responses = append(responses, response) log.Println("status:", response.Response.StatusCode()) } // TODO(tmc): compare responses, record information log.Println(len(responses), "shadow requests performed") }
// ServeHTTP satisfies the net/http.Handler interface func (s *Splitter) ServeHTTP(rw http.ResponseWriter, r *http.Request) { teeRW := httputils.NewTeeResponseWriter(rw) r2, err := httputils.CopyRequest(r) // on request copy error attempt and early exit (which may be invalid, unfortunately) if err != nil { log.Println("splitter: error copying request:", err) s.upstreamProxy.ServeHTTP(teeRW, r) return } reqresp := httputils.NewRequestResponse(r2, teeRW) defer reqresp.MarkDone() select { case s.requests <- reqresp: default: log.Println("splitter: dropped request on the floor") } s.upstreamProxy.ServeHTTP(teeRW, r) }