Skip to content

Commit

Permalink
Merge pull request #6 from fullpipe/livereload
Browse files Browse the repository at this point in the history
Livereload
  • Loading branch information
fullpipe authored Dec 7, 2019
2 parents 1f00f65 + a77489c commit 0703991
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 25 deletions.
35 changes: 28 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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`
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand All @@ -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=
Expand Down
83 changes: 67 additions & 16 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,41 @@ 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 <path to mocks>",
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])
if err != nil {
log.Fatal(err)
}

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)
}

collection.Append(mocks)
collection.Rebuild(files)

log.Println("Mocks found:", len(collection.mocks))
if Watch {
go watchAndRebuildCollection(files)
}

http.HandleFunc("/", errorHandler(httpHandler))
Expand All @@ -52,28 +49,82 @@ 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 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)
}
}
}()

for _, f := range files {
err = watcher.Add(f)
if err != nil {
log.Fatal(err)
}
}

<-done
}

func httpHandler(w http.ResponseWriter, r *http.Request) error {
body := GetBodyCopy(r)
body := getBodyCopy(r)

mock := collection.Lookup(r)
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
}

return ProcessMock(w, r, mock)
}

func GetBodyCopy(req *http.Request) []byte {
func getBodyCopy(req *http.Request) []byte {
bodyBytes, _ := ioutil.ReadAll(req.Body)
req.Body.Close()
req.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes))
Expand All @@ -86,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)
}
}
}
27 changes: 27 additions & 0 deletions mock.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package main

import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
"sync"
)

// Mock contains mock info
Expand All @@ -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
}
Expand All @@ -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()
}
2 changes: 1 addition & 1 deletion process_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (

// ProcessMock writes from mock to ResponseWriter
func ProcessMock(w http.ResponseWriter, r *http.Request, mock *Mock) error {
body := GetBodyCopy(r)
body := getBodyCopy(r)

if mock.Proxy != "" {
pr, _ := http.NewRequest(r.Method, r.URL.String(), ioutil.NopCloser(bytes.NewBuffer(body)))
Expand Down
2 changes: 1 addition & 1 deletion request.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (mockRequest Request) LooksLike(req *http.Request) bool {
log.Panicln(err)
}

body := GetBodyCopy(req)
body := getBodyCopy(req)

if !compareJSON(*mockRequest.JSON, body, dataType) {
return false
Expand Down

0 comments on commit 0703991

Please sign in to comment.