diff --git a/cmd/killgrave/main.go b/cmd/killgrave/main.go index b53389f..0f759bc 100644 --- a/cmd/killgrave/main.go +++ b/cmd/killgrave/main.go @@ -33,15 +33,16 @@ const ( func main() { var ( - host = flag.String("host", _defaultHost, "if you run your server on a different host") - port = flag.Int("port", _defaultPort, "port to run the server") - secure = flag.Bool("secure", _defaultSecure, "if you run your server using TLS (https)") - imposters = flag.String("imposters", _defaultImpostersPath, "directory where your imposters are saved") - showVersion = flag.Bool("version", false, "show the _version of the application") - configFilePath = flag.String("config", _defaultConfigFile, "path with configuration file") - watcherFlag = flag.Bool("watcher", false, "file watcher, reload the server with each file change") - proxyModeFlag = flag.String("proxy-mode", _defaultProxyMode.String(), "proxy mode you can choose between (all, missing or none)") - proxyURLFlag = flag.String("proxy-url", "", "proxy url, you need to choose a proxy-mode") + host = flag.String("host", _defaultHost, "if you run your server on a different host") + port = flag.Int("port", _defaultPort, "port to run the server") + secure = flag.Bool("secure", _defaultSecure, "if you run your server using TLS (https)") + imposters = flag.String("imposters", _defaultImpostersPath, "directory where your imposters are saved") + showVersion = flag.Bool("version", false, "show the _version of the application") + configFilePath = flag.String("config", _defaultConfigFile, "path with configuration file") + watcherFlag = flag.Bool("watcher", false, "file watcher, reload the server with each file change") + proxyModeFlag = flag.String("proxy-mode", _defaultProxyMode.String(), "proxy mode you can choose between (all, missing or none)") + proxyURLFlag = flag.String("proxy-url", "", "proxy url, you need to choose a proxy-mode") + dumpRequestsFlag = flag.Bool("dump-requests", false, "dumps the request performed against the the server") ) flag.Parse() @@ -60,6 +61,7 @@ func main() { killgrave.WithProxyConfiguration(*proxyModeFlag, *proxyURLFlag), killgrave.WithWatcherConfiguration(*watcherFlag), killgrave.WithConfigFile(*configFilePath), + killgrave.WithDumpRequestsConfiguration(*dumpRequestsFlag), ) if err != nil { log.Println(err) @@ -69,8 +71,7 @@ func main() { signal.Notify(done, syscall.SIGINT, syscall.SIGTERM) - var srv server.Server - srv = runServer(cfg.Host, cfg.Port, cfg) + srv := runServer(cfg.Host, cfg.Port, cfg) srv.Run() // Initialize and start the file watcher if the watcher option is true @@ -125,6 +126,7 @@ func runServer(host string, port int, cfg killgrave.Config) server.Server { httpServer, proxyServer, cfg.Secure, + cfg.DumpRequests, ) if err := s.Build(); err != nil { log.Fatal(err) diff --git a/go.sum b/go.sum index c0f305a..572b2cb 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= @@ -34,4 +35,5 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/config.go b/internal/config.go index 3c04220..62c50b8 100644 --- a/internal/config.go +++ b/internal/config.go @@ -18,6 +18,7 @@ type Config struct { Proxy ConfigProxy `yaml:"proxy"` Secure bool `yaml:"secure"` Watcher bool `yaml:"watcher"` + DumpRequests bool `yaml:"dump_requests"` } // ConfigCORS representation of section CORS of the yaml @@ -157,3 +158,12 @@ func WithWatcherConfiguration(watcher bool) ConfigOpt { return nil } } + +// WithDumpRequestConfiguration preparing server to dump the received requests +func WithDumpRequestsConfiguration(dumpRequests bool) ConfigOpt { + return func(cfg *Config) error { + + cfg.DumpRequests = dumpRequests + return nil + } +} diff --git a/internal/config_test.go b/internal/config_test.go index a259c8b..8f2b30c 100644 --- a/internal/config_test.go +++ b/internal/config_test.go @@ -27,7 +27,8 @@ func TestNewConfig(t *testing.T) { false, WithProxyConfiguration(ProxyNone.String(), ""), WithConfigFile(tc.input), - WithWatcherConfiguration(tc.expected.Watcher)) + WithWatcherConfiguration(tc.expected.Watcher), + WithDumpRequestsConfiguration(tc.expected.DumpRequests)) if err != nil && tc.err == nil { t.Fatalf("not expected any erros and got %v", err) @@ -121,7 +122,8 @@ func validConfig() Config { ExposedHeaders: []string{"Cache-Control"}, AllowCredentials: true, }, - Watcher: true, + Watcher: true, + DumpRequests: true, } } diff --git a/internal/server/http/handler.go b/internal/server/http/handler.go index 83ee85a..8cec2d6 100644 --- a/internal/server/http/handler.go +++ b/internal/server/http/handler.go @@ -4,13 +4,20 @@ import ( "io/ioutil" "log" "net/http" + "net/http/httputil" "os" "time" ) // ImposterHandler create specific handler for the received imposter func ImposterHandler(imposter Imposter) http.HandlerFunc { + shouldDumpRequest := imposter.Request.Dump return func(w http.ResponseWriter, r *http.Request) { + if shouldDumpRequest { + if dumped, err := httputil.DumpRequest(r, true); err == nil { + log.Print(string(dumped)) + } + } if imposter.Delay() > 0 { time.Sleep(imposter.Delay()) } diff --git a/internal/server/http/imposter.go b/internal/server/http/imposter.go index 88c8b1a..6ef4e70 100644 --- a/internal/server/http/imposter.go +++ b/internal/server/http/imposter.go @@ -55,6 +55,7 @@ type Request struct { SchemaFile *string `json:"schemaFile"` Params *map[string]string `json:"params"` Headers *map[string]string `json:"headers"` + Dump bool `json:"dump"` } // Response represent the structure of real response diff --git a/internal/server/http/server.go b/internal/server/http/server.go index 5782da2..951c9c4 100644 --- a/internal/server/http/server.go +++ b/internal/server/http/server.go @@ -41,16 +41,18 @@ type Server struct { httpServer *http.Server proxy *Proxy secure bool + dumpRequests bool } // NewServer initialize the mock server -func NewServer(p string, r *mux.Router, httpServer *http.Server, proxyServer *Proxy, secure bool) Server { +func NewServer(p string, r *mux.Router, httpServer *http.Server, proxyServer *Proxy, secure bool, dumpRequests bool) Server { return Server{ impostersPath: p, router: r, httpServer: httpServer, proxy: proxyServer, secure: secure, + dumpRequests: dumpRequests, } } @@ -165,6 +167,9 @@ func (s *Server) Shutdown() error { func (s *Server) addImposterHandler(imposters []Imposter, imposterConfig ImposterConfig) { for _, imposter := range imposters { imposter.BasePath = filepath.Dir(imposterConfig.FilePath) + if s.dumpRequests { + imposter.Request.Dump = true // Override imposter configuration + } r := s.router.HandleFunc(imposter.Request.Endpoint, ImposterHandler(imposter)). Methods(imposter.Request.Method). MatcherFunc(MatcherBySchema(imposter)) diff --git a/internal/server/http/server_test.go b/internal/server/http/server_test.go index e417d75..c89020d 100644 --- a/internal/server/http/server_test.go +++ b/internal/server/http/server_test.go @@ -1,6 +1,7 @@ package http import ( + "bytes" "crypto/tls" "errors" "io" @@ -9,6 +10,7 @@ import ( "net/http" "net/http/httptest" "os" + "strings" "testing" "time" @@ -28,9 +30,9 @@ func TestServer_Build(t *testing.T) { server Server err error }{ - {"imposter directory not found", NewServer("failImposterPath", nil, &http.Server{}, &Proxy{}, false), errors.New("hello")}, - {"malformatted json", NewServer("test/testdata/malformatted_imposters", nil, &http.Server{}, &Proxy{}, false), nil}, - {"valid imposter", NewServer("test/testdata/imposters", mux.NewRouter(), &http.Server{}, &Proxy{}, false), nil}, + {"imposter directory not found", NewServer("failImposterPath", nil, &http.Server{}, &Proxy{}, false, false), errors.New("hello")}, + {"malformatted json", NewServer("test/testdata/malformatted_imposters", nil, &http.Server{}, &Proxy{}, false, false), nil}, + {"valid imposter", NewServer("test/testdata/imposters", mux.NewRouter(), &http.Server{}, &Proxy{}, false, false), nil}, } for _, tt := range serverData { @@ -64,7 +66,7 @@ func TestBuildProxyMode(t *testing.T) { if err != nil { t.Fatal("NewProxy failed: ", err) } - server := NewServer("test/testdata/imposters", router, httpServer, proxyServer, false) + server := NewServer("test/testdata/imposters", router, httpServer, proxyServer, false, false) return &server, func() { httpServer.Close() } @@ -145,7 +147,7 @@ func TestBuildSecureMode(t *testing.T) { if err != nil { t.Fatal("NewProxy failed: ", err) } - server := NewServer("test/testdata/imposters_secure", router, httpServer, proxyServer, true) + server := NewServer("test/testdata/imposters_secure", router, httpServer, proxyServer, true, false) return &server, func() { httpServer.Close() } @@ -208,3 +210,57 @@ func TestBuildSecureMode(t *testing.T) { }) } } + +func TestBuildDumpRequests(t *testing.T) { + var buf bytes.Buffer + log.SetOutput(&buf) + defer func() { + log.SetOutput(os.Stderr) + }() + + server := NewServer("test/testdata/imposters", mux.NewRouter(), &http.Server{}, &Proxy{}, false, false) + if err := server.Build(); err != nil { + t.Fatalf("not expected any erros and got %+v", err) + } + + expectedBody := "Dumped" + req := httptest.NewRequest("GET", "/yamlTestDumpRequest", strings.NewReader(expectedBody)) + w := httptest.NewRecorder() + + server.router.ServeHTTP(w, req) + response := w.Result() + if response.StatusCode != http.StatusOK { + t.Errorf("Expected status code: %v, got: %v", http.StatusOK, response.StatusCode) + } + + if !strings.Contains(buf.String(), expectedBody) { + t.Errorf("Expect request dumped on logs failed") + } +} + +func TestBuildDumpRequestsOverride(t *testing.T) { + var buf bytes.Buffer + log.SetOutput(&buf) + defer func() { + log.SetOutput(os.Stderr) + }() + + server := NewServer("test/testdata/imposters", mux.NewRouter(), &http.Server{}, &Proxy{}, false, true) + if err := server.Build(); err != nil { + t.Fatalf("not expected any erros and got %+v", err) + } + + expectedBody := "Dumped" + req := httptest.NewRequest("GET", "/yamlTestRequest", strings.NewReader(expectedBody)) + w := httptest.NewRecorder() + + server.router.ServeHTTP(w, req) + response := w.Result() + if response.StatusCode != http.StatusOK { + t.Errorf("Expected status code: %v, got: %v", http.StatusOK, response.StatusCode) + } + + if !strings.Contains(buf.String(), expectedBody) { + t.Errorf("Expect request dumped on logs failed") + } +} diff --git a/internal/server/http/test/testdata/imposters/test_request.imp.yaml b/internal/server/http/test/testdata/imposters/test_request.imp.yaml index 0d49b8e..2a8d60e 100644 --- a/internal/server/http/test/testdata/imposters/test_request.imp.yaml +++ b/internal/server/http/test/testdata/imposters/test_request.imp.yaml @@ -4,4 +4,11 @@ endpoint: /yamlTestRequest response: status: 200 - body: "Yaml Handled" \ No newline at end of file + body: "Yaml Handled" +- request: + method: GET + endpoint: /yamlTestDumpRequest + dump: true + response: + status: 200 + body: "Yaml Dump Handled" \ No newline at end of file diff --git a/internal/test/testdata/config.yml b/internal/test/testdata/config.yml index 774492f..a1df74b 100644 --- a/internal/test/testdata/config.yml +++ b/internal/test/testdata/config.yml @@ -8,3 +8,4 @@ cors: origins: ["*"] allow_credentials: true watcher: true +dump_requests: true