From a77489c4bdfc080a82bc0d55592ddf463add164f Mon Sep 17 00:00:00 2001 From: Eugene Bravov Date: Sat, 7 Dec 2019 22:48:14 +0300 Subject: [PATCH] Add livereload --- README.md | 35 ++++++++++++++++++++----- go.mod | 2 ++ go.sum | 4 +++ main.go | 77 ++++++++++++++++++++++++++++++++++++++++++++----------- mock.go | 27 +++++++++++++++++++ 5 files changed, 123 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 4337c16..af56feb 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,6 @@ Install binary go get -u github.com/fullpipe/jmock ``` -or use with docker - -``` -docker run -p 9090:9090 -v ${PWD}/mocks:/mocks fullpipe/jmock -``` - ## Usage First create mocks collection file some where in your project. Use [standard @@ -67,7 +61,7 @@ request matching. For example `./mocks/users.json`: Start jmock server ```bash -jmock ./mocks/*.json --port 9090 +jmock "./mocks/*.json" --port 9090 --watch ``` Thats it your fake api is ready. Check the request @@ -83,6 +77,33 @@ Output } ``` +### Usage with docker + +Run mock server + +``` +docker run -p 9090:9090 -v ${PWD}/mocks:/mocks fullpipe/jmock +``` + +Or if you need to watch files + +``` +docker run -p 9090:9090 -v ${PWD}/mocks:/mocks fullpipe/jmock /mocks/*.json --port 9090 --watch +``` + +Or with docker-compose + +```yaml +services: + api: + image: fullpipe/jmock + command: "/mocks/*.json --port 9090 --watch" + ports: + - "9090:9090" + volumes: + - ./mocks:/mocks +``` + ## Mocks Mock consists of 3 blocks `request`, `response`, `proxy` diff --git a/go.mod b/go.mod index 06ee2c9..b2f5735 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,9 @@ go 1.12 require ( github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23 + github.com/fsnotify/fsnotify v1.4.7 github.com/gobwas/glob v0.2.3 github.com/spf13/cobra v0.0.5 github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e ) diff --git a/go.sum b/go.sum index d6f89ef..4f1343a 100644 --- a/go.sum +++ b/go.sum @@ -7,6 +7,7 @@ github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8Nz github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= @@ -33,6 +34,9 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/main.go b/main.go index 3fdd863..18f2da2 100644 --- a/main.go +++ b/main.go @@ -2,26 +2,30 @@ package main import ( "bytes" - "encoding/json" "fmt" "io/ioutil" "log" "net/http" "os" "path/filepath" + "time" + "github.com/fsnotify/fsnotify" "github.com/spf13/cobra" + "golang.org/x/sync/semaphore" ) var collection MockCollection func main() { var Port int + var Watch bool + var rootCmd = &cobra.Command{ Use: "jmock ", Short: "Simple and easy to use json/post API mock server", Long: `Simple and easy to use json/post API mock server`, - Version: "0.0.1", + Version: "0.1.0", Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { files, err := filepath.Glob(args[0]) @@ -29,7 +33,11 @@ func main() { log.Fatal(err) } - buildCollection(files) + collection.Rebuild(files) + + if Watch { + go watchAndRebuildCollection(files) + } http.HandleFunc("/", errorHandler(httpHandler)) log.Println("Listening on port", fmt.Sprintf(":%d", Port)) @@ -41,25 +49,64 @@ func main() { } rootCmd.Flags().IntVarP(&Port, "port", "p", 9090, "Specify port to listen") + rootCmd.Flags().BoolVarP(&Watch, "watch", "w", false, "Watch for file changes") + if err := rootCmd.Execute(); err != nil { log.Fatal(err) os.Exit(1) } } -func buildCollection(files []string) { - for _, f := range files { - temp, _ := ioutil.ReadFile(f) - var mocks []Mock - - if err := json.Unmarshal(temp, &mocks); err != nil { - log.Printf("Unable to parse %s file", f) +func watchAndRebuildCollection(files []string) { + log.Printf("Watching for %d files\n", len(files)) + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Fatal(err) + } + defer watcher.Close() + + var sem = semaphore.NewWeighted(1) + + done := make(chan bool) + go func() { + for { + select { + case event, ok := <-watcher.Events: + if !ok { + log.Fatal("Watcher`s event failed") + } + + if event.Op.String() != "WRITE" { + continue + } + + if !sem.TryAcquire(1) { + continue + } + + go func() { + log.Println("Changes detected. Updating mocks...") + time.Sleep(100 * time.Millisecond) + collection.Rebuild(files) + sem.Release(1) + }() + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Println("Watch error:", err) + } } + }() - collection.Append(mocks) - - log.Println("Mocks found:", len(collection.mocks)) + for _, f := range files { + err = watcher.Add(f) + if err != nil { + log.Fatal(err) + } } + + <-done } func httpHandler(w http.ResponseWriter, r *http.Request) error { @@ -69,7 +116,7 @@ func httpHandler(w http.ResponseWriter, r *http.Request) error { r.Body = ioutil.NopCloser(bytes.NewBuffer(body)) if mock == nil { - log.Println("No mock found for request") + log.Println("Mock not found for request") w.WriteHeader(501) return nil } @@ -90,7 +137,7 @@ func errorHandler(f func(http.ResponseWriter, *http.Request) error) http.Handler err := f(w, r) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) - log.Printf("Error handling %q: %v", r.RequestURI, err) + log.Printf("Error %q: %v", r.RequestURI, err) } } } diff --git a/mock.go b/mock.go index b6fd269..c5c7e96 100644 --- a/mock.go +++ b/mock.go @@ -1,7 +1,11 @@ package main import ( + "encoding/json" + "io/ioutil" + "log" "net/http" + "sync" ) // Mock contains mock info @@ -13,12 +17,15 @@ type Mock struct { // MockCollection for work with mocks type MockCollection struct { + mutex sync.Mutex mocks []Mock } // Append mock to mock collection func (c *MockCollection) Append(m []Mock) *MockCollection { + c.mutex.Lock() c.mocks = append(c.mocks, m...) + c.mutex.Unlock() return c } @@ -32,3 +39,23 @@ func (c *MockCollection) Lookup(r *http.Request) *Mock { } return nil } + +func (c *MockCollection) Rebuild(files []string) { + c.mutex.Lock() + + c.mocks = []Mock{} + for _, f := range files { + temp, _ := ioutil.ReadFile(f) + var mocks []Mock + + if err := json.Unmarshal(temp, &mocks); err != nil { + log.Printf("Unable to parse %s file", f) + } + + c.mocks = append(c.mocks, mocks...) + } + + log.Println("Mocks found:", len(c.mocks)) + + c.mutex.Unlock() +}