// save gets request fingerprint, extracts request body, status code and headers, then saves it to cache func (hf *Hoverfly) save(req *http.Request, reqBody []byte, resp *http.Response, respBody []byte) { if resp == nil { resp = emptyResp } else { responseObj := models.ResponseDetails{ Status: resp.StatusCode, Body: string(respBody), Headers: resp.Header, } requestObj := models.RequestDetails{ Path: req.URL.Path, Method: req.Method, Destination: req.Host, Scheme: req.URL.Scheme, Query: req.URL.RawQuery, Body: string(reqBody), Headers: req.Header, } pair := models.RequestResponsePair{ Response: responseObj, Request: requestObj, } err := hf.RequestMatcher.SaveRequestResponsePair(&pair) if err != nil { log.WithFields(log.Fields{ "error": err.Error(), }).Error("Failed to save payload") } pairBytes, err := pair.Encode() if err != nil { log.WithFields(log.Fields{ "error": err.Error(), }).Error("Failed to serialize payload") } else { // hook var en Entry en.ActionType = ActionTypeRequestCaptured en.Message = "captured" en.Time = time.Now() en.Data = pairBytes if err := hf.Hooks.Fire(ActionTypeRequestCaptured, &en); err != nil { log.WithFields(log.Fields{ "error": err.Error(), "message": en.Message, "actionType": ActionTypeRequestCaptured, }).Error("failed to fire hook") } } } }
func TestRequestResponsePairEncodeEmpty(t *testing.T) { RegisterTestingT(t) pair := models.RequestResponsePair{} pairBytes, err := pair.Encode() Expect(err).To(BeNil()) _, err = models.NewRequestResponsePairFromBytes(pairBytes) Expect(err).To(BeNil()) }
func Test_rebuildHashes_whenDataIsHashedForAProxy_andStillAProxy_keysAreNotChanged(t *testing.T) { RegisterTestingT(t) webserver := false db := cache.NewInMemoryCache() pair := models.RequestResponsePair{ Request: models.RequestDetails{ Path: "/hello", Destination: "a-host.com", }, Response: models.ResponseDetails{ Body: "a body", }, } pairBytes, _ := pair.Encode() db.Set([]byte(pair.Id()), pairBytes) rebuildHashes(db, webserver) result, err := db.Get([]byte(pair.Id())) Expect(err).To(BeNil()) Expect(result).To(Equal(pairBytes)) }
func TestRequestResponsePairEncodeDecode(t *testing.T) { RegisterTestingT(t) resp := models.ResponseDetails{ Status: 200, Body: "body here", } pair := models.RequestResponsePair{Response: resp} pairBytes, err := pair.Encode() Expect(err).To(BeNil()) pairFromBytes, err := models.NewRequestResponsePairFromBytes(pairBytes) Expect(err).To(BeNil()) Expect(pairFromBytes.Response.Body).To(Equal(resp.Body)) Expect(pairFromBytes.Response.Status).To(Equal(resp.Status)) }
func ExecuteMiddlewareRemotely(middleware string, pair models.RequestResponsePair) (models.RequestResponsePair, error) { pairViewBytes, err := json.Marshal(pair.ConvertToRequestResponsePairView()) req, err := http.NewRequest("POST", middleware, bytes.NewBuffer(pairViewBytes)) if err != nil { log.WithFields(log.Fields{ "error": err.Error(), }).Error("Error when building request to remote middleware") return pair, err } resp, err := http.DefaultClient.Do(req) if err != nil { log.WithFields(log.Fields{ "error": err.Error(), }).Error("Error when communicating with remote middleware") return pair, err } if resp.StatusCode != 200 { log.Error("Remote middleware did not process payload") return pair, errors.New("Error when communicating with remote middleware") } returnedPairViewBytes, err := ioutil.ReadAll(resp.Body) if err != nil { log.WithFields(log.Fields{ "error": err.Error(), }).Error("Error when process response from remote middleware") return pair, err } var newPairView views.RequestResponsePairView err = json.Unmarshal(returnedPairViewBytes, &newPairView) if err != nil { log.WithFields(log.Fields{ "error": err.Error(), }).Error("Error when trying to serialize response from remote middleware") return pair, err } return models.NewRequestResponsePairFromRequestResponsePairView(newPairView), nil }
func TestHoverflyGetSimulationReturnsMultipleRequestResponsePairs(t *testing.T) { RegisterTestingT(t) server, unit := testTools(201, `{'message': 'here'}`) defer server.Close() recording := models.RequestResponsePair{ Request: models.RequestDetails{ Destination: "testhost.com", Path: "/test", }, Response: models.ResponseDetails{ Status: 200, Body: "test", }, } recordingBytes, err := recording.Encode() Expect(err).To(BeNil()) unit.RequestCache.Set([]byte("key"), recordingBytes) unit.RequestCache.Set([]byte("key2"), recordingBytes) simulation, err := unit.GetSimulation() Expect(err).To(BeNil()) Expect(simulation.DataView.RequestResponsePairs).To(HaveLen(2)) Expect(*simulation.DataView.RequestResponsePairs[0].Request.Destination).To(Equal("testhost.com")) Expect(*simulation.DataView.RequestResponsePairs[0].Request.Path).To(Equal("/test")) Expect(*simulation.DataView.RequestResponsePairs[0].Request.RequestType).To(Equal("recording")) Expect(simulation.DataView.RequestResponsePairs[0].Response.Status).To(Equal(200)) Expect(simulation.DataView.RequestResponsePairs[0].Response.Body).To(Equal("test")) Expect(*simulation.DataView.RequestResponsePairs[1].Request.Destination).To(Equal("testhost.com")) Expect(*simulation.DataView.RequestResponsePairs[1].Request.Path).To(Equal("/test")) Expect(*simulation.DataView.RequestResponsePairs[1].Request.RequestType).To(Equal("recording")) Expect(simulation.DataView.RequestResponsePairs[1].Response.Status).To(Equal(200)) Expect(simulation.DataView.RequestResponsePairs[1].Response.Body).To(Equal("test")) }
func (this *RequestMatcher) SaveRequestResponsePair(pair *models.RequestResponsePair) error { var key string if *this.Webserver { key = pair.IdWithoutHost() } else { key = pair.Id() } log.WithFields(log.Fields{ "path": pair.Request.Path, "rawQuery": pair.Request.Query, "requestMethod": pair.Request.Method, "bodyLen": len(pair.Request.Body), "destination": pair.Request.Destination, "hashKey": key, }).Debug("Capturing") pairBytes, err := pair.Encode() if err != nil { return err } return this.RequestCache.Set([]byte(key), pairBytes) }
// ExecuteMiddleware - takes command (middleware string) and payload, which is passed to middleware func ExecuteMiddlewareLocally(middlewares string, pair models.RequestResponsePair) (models.RequestResponsePair, error) { mws := strings.Split(middlewares, "|") var cmdList []*exec.Cmd for _, v := range mws { commands := strings.Split(strings.TrimSpace(v), " ") cmd := exec.Command(commands[0], commands[1:]...) cmdList = append(cmdList, cmd) } // getting payload pairViewBytes, err := json.Marshal(pair.ConvertToRequestResponsePairView()) if log.GetLevel() == log.DebugLevel { log.WithFields(log.Fields{ "middlewares": mws, "count": len(mws), "payload": string(pairViewBytes), }).Debug("preparing to modify payload") } if err != nil { log.WithFields(log.Fields{ "error": err.Error(), }).Error("Failed to marshal json") return pair, err } // cmdList[0].Stdin = bytes.NewReader(pairViewBytes) // Run the pipeline mwOutput, stderr, err := Pipeline(cmdList...) // middleware failed to execute if err != nil { if len(stderr) > 0 { log.WithFields(log.Fields{ "sdtderr": string(stderr), "error": err.Error(), }).Error("Middleware error") } else { log.WithFields(log.Fields{ "error": err.Error(), }).Error("Middleware error") } return pair, err } // log stderr, middleware executed successfully if len(stderr) > 0 { log.WithFields(log.Fields{ "sdtderr": string(stderr), }).Info("Information from middleware") } if len(mwOutput) > 0 { var newPairView views.RequestResponsePairView err = json.Unmarshal(mwOutput, &newPairView) if err != nil { log.WithFields(log.Fields{ "mwOutput": string(mwOutput), "error": err.Error(), }).Error("Failed to unmarshal JSON from middleware") } else { if log.GetLevel() == log.DebugLevel { log.WithFields(log.Fields{ "middlewares": middlewares, "count": len(middlewares), "payload": string(mwOutput), }).Debug("payload after modifications") } // payload unmarshalled into RequestResponsePair struct, returning it return models.NewRequestResponsePairFromRequestResponsePairView(newPairView), nil } } else { log.WithFields(log.Fields{ "mwOutput": string(mwOutput), }).Warn("No response from middleware.") } return pair, nil }
// doRequest performs original request and returns response that should be returned to client and error (if there is one) func (hf *Hoverfly) doRequest(request *http.Request) (*http.Request, *http.Response, error) { // We can't have this set. And it only contains "/pkg/net/http/" anyway request.RequestURI = "" if hf.Cfg.Middleware.FullCommand != "" { // middleware is provided, modifying request var requestResponsePair models.RequestResponsePair rd, err := models.NewRequestDetailsFromHttpRequest(request) if err != nil { return nil, nil, err } requestResponsePair.Request = rd c := NewConstructor(request, requestResponsePair) err = c.ApplyMiddleware(&hf.Cfg.Middleware) if err != nil { log.WithFields(log.Fields{ "mode": hf.Cfg.Mode, "error": err.Error(), "host": request.Host, "method": request.Method, "path": request.URL.Path, }).Error("could not forward request, middleware failed to modify request.") return nil, nil, err } request, err = c.ReconstructRequest() if err != nil { return nil, nil, err } } requestBody, _ := ioutil.ReadAll(request.Body) request.Body = ioutil.NopCloser(bytes.NewReader(requestBody)) resp, err := hf.HTTP.Do(request) request.Body = ioutil.NopCloser(bytes.NewReader(requestBody)) if err != nil { log.WithFields(log.Fields{ "mode": hf.Cfg.Mode, "error": err.Error(), "host": request.Host, "method": request.Method, "path": request.URL.Path, }).Error("could not forward request, failed to do an HTTP request.") return nil, nil, err } log.WithFields(log.Fields{ "mode": hf.Cfg.Mode, "host": request.Host, "method": request.Method, "path": request.URL.Path, }).Debug("response from external service got successfuly!") resp.Header.Set("hoverfly", "Was-Here") return request, resp, nil }
// ManualAddHandler - manually add new request/responses, using a form func (d *Hoverfly) ManualAddHandler(w http.ResponseWriter, req *http.Request, next http.HandlerFunc) { err := req.ParseForm() if err != nil { log.WithFields(log.Fields{ "error": err.Error(), }).Error("Got error while parsing form") } // request details destination := req.PostFormValue("inputDestination") method := req.PostFormValue("inputMethod") path := req.PostFormValue("inputPath") query := req.PostFormValue("inputQuery") reqBody := req.PostFormValue("inputRequestBody") preq := models.RequestDetails{ Destination: destination, Method: method, Path: path, Query: query, Body: reqBody} // response respStatusCode := req.PostFormValue("inputResponseStatusCode") respBody := req.PostFormValue("inputResponseBody") contentType := req.PostFormValue("inputContentType") headers := make(map[string][]string) // getting content type if contentType == "xml" { headers["Content-Type"] = []string{"application/xml"} } else if contentType == "json" { headers["Content-Type"] = []string{"application/json"} } else { headers["Content-Type"] = []string{"text/html"} } sc, _ := strconv.Atoi(respStatusCode) presp := models.ResponseDetails{ Status: sc, Headers: headers, Body: respBody, } log.WithFields(log.Fields{ "respBody": respBody, "contentType": contentType, }).Info("manually adding request/response") p := models.RequestResponsePair{Request: preq, Response: presp} var pairViews []views.RequestResponsePairView pairViews = append(pairViews, *p.ConvertToRequestResponsePairView()) err = d.ImportRequestResponsePairViews(pairViews) w.Header().Set("Content-Type", "application/json") var response messageResponse if err != nil { response.Message = fmt.Sprintf("Got error: %s", err.Error()) w.WriteHeader(400) } else { // redirecting to home response.Message = "Record added successfuly" w.WriteHeader(201) } b, err := response.Encode() if err != nil { // failed to read response body log.WithFields(log.Fields{ "error": err.Error(), }).Error("Could not encode response body!") http.Error(w, "Failed to encode response", 500) return } w.Write(b) }
// ExecuteMiddleware - takes command (middleware string) and payload, which is passed to middleware func (this Middleware) executeMiddlewareLocally(pair models.RequestResponsePair) (models.RequestResponsePair, error) { var commandAndArgs []string if this.Binary == "" { commandAndArgs = strings.Split(strings.TrimSpace(this.FullCommand), " ") } else { commandAndArgs = []string{this.Binary, this.Script.Name()} } middlewareCommand := exec.Command(commandAndArgs[0], commandAndArgs[1:]...) // getting payload pairViewBytes, err := json.Marshal(pair.ConvertToRequestResponsePairView()) if err != nil { log.WithFields(log.Fields{ "error": err.Error(), }).Error("Failed to marshal json") return pair, err } if log.GetLevel() == log.DebugLevel { log.WithFields(log.Fields{ "middleware": this.FullCommand, "stdin": string(pairViewBytes), }).Debug("preparing to modify payload") } var stdout bytes.Buffer var stderr bytes.Buffer // Redirect standard streams middlewareCommand.Stdin = bytes.NewReader(pairViewBytes) middlewareCommand.Stdout = &stdout middlewareCommand.Stderr = &stderr if err := middlewareCommand.Start(); err != nil { log.WithFields(log.Fields{ "sdtdout": string(stdout.Bytes()), "sdtderr": string(stderr.Bytes()), "error": err.Error(), }).Error("Middleware failed to start") return pair, err } if err := middlewareCommand.Wait(); err != nil { log.WithFields(log.Fields{ "sdtdout": string(stdout.Bytes()), "sdtderr": string(stderr.Bytes()), "error": err.Error(), }).Error("Middleware failed to stop successfully") return pair, err } // log stderr, middleware executed successfully if len(stderr.Bytes()) > 0 { log.WithFields(log.Fields{ "sdtderr": string(stderr.Bytes()), }).Info("Information from middleware") } if len(stdout.Bytes()) > 0 { var newPairView v2.RequestResponsePairView err = json.Unmarshal(stdout.Bytes(), &newPairView) if err != nil { log.WithFields(log.Fields{ "stdout": string(stdout.Bytes()), "error": err.Error(), }).Error("Failed to unmarshal JSON from middleware") } else { if log.GetLevel() == log.DebugLevel { log.WithFields(log.Fields{ "middleware": this.FullCommand, "payload": string(stdout.Bytes()), }).Debug("payload after modifications") } // payload unmarshalled into RequestResponsePair struct, returning it return models.NewRequestResponsePairFromRequestResponsePairView(newPairView), nil } } else { log.WithFields(log.Fields{ "stdout": string(stdout.Bytes()), }).Warn("No response from middleware.") } return pair, nil }