diff --git a/.dockerignore b/.dockerignore index 23ee03b..1707db9 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,2 @@ -mock \ No newline at end of file +mock +Dockerfile \ No newline at end of file diff --git a/README.md b/README.md index 866e3a8..2c254f1 100644 --- a/README.md +++ b/README.md @@ -2,24 +2,19 @@ > [Wiremock](https://hub.docker.com/r/prongbang/wiremock) Minimal Mock your APIs -## How to run - -### Run with Docker - -#### Version 1.0.+ - -Support matching routes with [gorilla/mux](https://github.com/gorilla/mux#matching-routes) +```shell script + _ ___ __ + | | /| / (_)______ __ _ ___ ____/ /__ + | |/ |/ / / __/ -_) ' \/ _ \/ __/ '_/ + |__/|__/_/_/ \__/_/_/_/\___/\__/_/\_\ -```shell -docker pull prongbang/wiremock:1.3.1 + -> wiremock server started on :8000 ``` -#### Version 2.0.+ - -Support matching routes with [gofiber/fiber](https://docs.gofiber.io/guide/routing) +### Run with Docker ```shell -docker pull prongbang/wiremock:2.0.1 +docker pull prongbang/wiremock:latest ``` ### Run with Docker Compose @@ -58,24 +53,13 @@ $ wiremock $ wiremock -port=9000 ``` -- Running - -```shell script - _ ___ __ - | | /| / (_)______ __ _ ___ ____/ /__ - | |/ |/ / / __/ -_) ' \/ _ \/ __/ '_/ - |__/|__/_/_/ \__/_/_/_/\___/\__/_/\_\ - - -> wiremock server started on :8000 -``` - ### Example Project [https://github.com/prongbang/wiremock-example](https://github.com/prongbang/wiremock-example) -## Matching Routes using gofiber/fiber +## Matching Routes using gorilla/mux -Read doc [https://docs.gofiber.io/guide/routing](https://docs.gofiber.io/guide/routing) +Read doc [gorilla/mux](https://github.com/gorilla/mux#matching-routes) ## Setup project @@ -137,7 +121,7 @@ routes: get_user: request: method: "GET" - url: "/api/v1/user/:id" + url: "/api/v1/user/{id:[0-9]+}" response: status: 200 body_file: user.json diff --git a/go.mod b/go.mod index 98f789e..a000716 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,7 @@ module github.com/prongbang/wiremock/v2 go 1.16 require ( - github.com/gofiber/fiber/v2 v2.27.0 - github.com/klauspost/compress v1.14.3 // indirect - golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect + github.com/gorilla/handlers v1.5.1 + github.com/gorilla/mux v1.8.0 gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 8700b42..ee67a31 100644 --- a/go.sum +++ b/go.sum @@ -1,29 +1,9 @@ -github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= -github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= -github.com/gofiber/fiber/v2 v2.27.0 h1:u34t1nOea7zz4jcZDK7+ZMiG+MVFYrHqMhTdYQDiFA8= -github.com/gofiber/fiber/v2 v2.27.0/go.mod h1:0bPXdTu+jRqINrEq1T6mHeVBnE0lQd67PGu35jD3hLk= -github.com/klauspost/compress v1.14.1/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.14.3 h1:DQv1WP+iS4srNjibdnHtqu8JNWCDMluj5NzPnFJsnvk= -github.com/klauspost/compress v1.14.3/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.33.0 h1:mHBKd98J5NcXuBddgjvim1i3kWzlng1SzLhrnBOU9g8= -github.com/valyala/fasthttp v1.33.0/go.mod h1:KJRK/MXx0J+yd0c5hlR+s1tIHD72sniU8ZJjl97LIw4= -github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= -github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= -golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220111093109-d55c255bac03/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= +github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= diff --git a/pkg/api/api.go b/pkg/api/api.go index 7519762..22f22dd 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -2,12 +2,12 @@ package api import ( "fmt" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/fiber/v2/middleware/cors" - "strings" - + "github.com/gorilla/handlers" + "github.com/gorilla/mux" "github.com/prongbang/wiremock/v2/pkg/config" "github.com/prongbang/wiremock/v2/pkg/status" + "log" + "net/http" ) type API interface { @@ -21,38 +21,18 @@ type api struct { func (a *api) Register(cfg config.Config) { status.Banner() - conf := fiber.Config{ - DisableStartupMessage: true, - } - app := fiber.New(conf) - - // Middleware - app.Use(cors.New(cors.Config{ - AllowOrigins: "*", - AllowHeaders: "*", - AllowMethods: strings.Join([]string{ - fiber.MethodGet, - fiber.MethodPost, - fiber.MethodHead, - fiber.MethodPut, - fiber.MethodDelete, - fiber.MethodPatch, - fiber.MethodOptions, - fiber.MethodTrace, - fiber.MethodConnect, - }, ","), - })) + r := mux.NewRouter() + headers := handlers.AllowedHeaders([]string{"*"}) + methods := handlers.AllowedMethods([]string{http.MethodGet, http.MethodPost, http.MethodHead, http.MethodPut, http.MethodPatch, http.MethodTrace, http.MethodDelete, http.MethodOptions}) + origins := handlers.AllowedOrigins([]string{"*"}) // Router - a.Router.Initials(app) + a.Router.Initials(r) status.Started(cfg.Port) // Listening - err := app.Listen(fmt.Sprintf(":%s", cfg.Port)) - if err != nil { - panic(err) - } + log.Fatal(http.ListenAndServe(fmt.Sprintf(":%s", cfg.Port), handlers.CORS(headers, methods, origins)(r))) } // NewAPI provide apis diff --git a/pkg/api/home/handler.go b/pkg/api/home/handler.go index 12233f5..4f52a03 100644 --- a/pkg/api/home/handler.go +++ b/pkg/api/home/handler.go @@ -2,20 +2,20 @@ package home import ( "fmt" - "github.com/gofiber/fiber/v2" "github.com/prongbang/wiremock/v2/pkg/config" + "net/http" ) type Handler interface { - GetHome(c *fiber.Ctx) error + GetHome(w http.ResponseWriter, r *http.Request) } type handler struct { Cfg config.Config } -func (h *handler) GetHome(c *fiber.Ctx) error { - return c.SendString(fmt.Sprintf("Wiremock server started on %s", h.Cfg.Port)) +func (h *handler) GetHome(w http.ResponseWriter, r *http.Request) { + _, _ = fmt.Fprint(w, "Wiremock server started on "+h.Cfg.Port) } func NewHandler(cfg config.Config) Handler { diff --git a/pkg/api/home/router.go b/pkg/api/home/router.go index 61bfba5..c0c232b 100644 --- a/pkg/api/home/router.go +++ b/pkg/api/home/router.go @@ -1,19 +1,19 @@ package home import ( - "github.com/gofiber/fiber/v2" + "github.com/gorilla/mux" ) type Router interface { - Initial(app *fiber.App) + Initial(route *mux.Router) } type router struct { Handle Handler } -func (r *router) Initial(app *fiber.App) { - app.Get("/", r.Handle.GetHome) +func (r *router) Initial(route *mux.Router) { + route.HandleFunc("/", r.Handle.GetHome).Methods("GET") } func NewRouter(handle Handler) Router { diff --git a/pkg/api/routers.go b/pkg/api/routers.go index a41e2c8..ba79aed 100644 --- a/pkg/api/routers.go +++ b/pkg/api/routers.go @@ -1,13 +1,13 @@ package api import ( - "github.com/gofiber/fiber/v2" + "github.com/gorilla/mux" "github.com/prongbang/wiremock/v2/pkg/api/home" "github.com/prongbang/wiremock/v2/pkg/api/wiremock" ) type Routers interface { - Initials(app *fiber.App) + Initials(route *mux.Router) } type routers struct { @@ -15,9 +15,9 @@ type routers struct { WiremockRoute wiremock.Router } -func (r *routers) Initials(app *fiber.App) { - r.HomeRoute.Initial(app) - r.WiremockRoute.Initial(app) +func (r *routers) Initials(route *mux.Router) { + r.HomeRoute.Initial(route) + r.WiremockRoute.Initial(route) } func NewRouters( diff --git a/pkg/api/wiremock/handler.go b/pkg/api/wiremock/handler.go index ff0cf20..5186ffc 100644 --- a/pkg/api/wiremock/handler.go +++ b/pkg/api/wiremock/handler.go @@ -2,7 +2,6 @@ package wiremock import ( "fmt" - "github.com/gofiber/fiber/v2" "net/http" "github.com/prongbang/wiremock/v2/pkg/core" @@ -10,7 +9,7 @@ import ( // Handler is a model for handler router type Handler interface { - Handle(c *fiber.Ctx) error + Handle(w http.ResponseWriter, r *http.Request) } type handler struct { @@ -18,24 +17,29 @@ type handler struct { Routers Routers } -func (h *handler) Handle(c *fiber.Ctx) error { +func (h *handler) Handle(w http.ResponseWriter, r *http.Request) { + // Reading form values + maxMemory := 32 << 20 // 32Mb + if err := r.ParseMultipartForm(int64(maxMemory)); err != nil { + _ = r.ParseForm() + } // Prepared request - httpHeader := core.BindHeader(h.Routers.Request.Header, c) + httpHeader := core.BindHeader(h.Routers.Request.Header, r) // Prepared response if len(h.Routers.Response.Header) == 0 { - c.Response().Header.Set("Content-Type", "application/json") + w.Header().Set("Content-Type", "application/json") } for k, v := range h.Routers.Response.Header { - c.Set(k, fmt.Sprintf("%v", v)) + w.Header().Set(k, fmt.Sprintf("%v", v)) } // Process cases matching if len(h.Routers.Request.Cases) > 0 { // Process cases matching - matching := h.UseCase.CasesMatching(c, h.Routers.Response.FileName, h.Routers.Request.Cases, Parameters{ + matching := h.UseCase.CasesMatching(r, h.Routers.Response.FileName, h.Routers.Request.Cases, Parameters{ ReqHeader: ReqHeader{ HttpHeader: httpHeader, MockHeader: h.Routers.Request.Header, @@ -45,14 +49,17 @@ func (h *handler) Handle(c *fiber.Ctx) error { // Process response if matching.IsMatch { response := h.UseCase.GetMockResponse(matching.Case.Response) - return c.Status(matching.Case.Response.Status).SendString(string(response)) + w.WriteHeader(matching.Case.Response.Status) + _, _ = w.Write(response) + } else { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write(matching.Result) } - - return c.Status(http.StatusBadRequest).SendString(string(matching.Result)) + return } // Prepared request - body := core.BindBody(h.Routers.Request.Body, c) + body := core.BindBody(h.Routers.Request.Body, r) // Process parameter matching matching := h.UseCase.ParameterMatching(Parameters{ @@ -69,10 +76,12 @@ func (h *handler) Handle(c *fiber.Ctx) error { // Prepared response if matching.IsMatch { response := h.UseCase.GetMockResponse(h.Routers.Response) - return c.Status(h.Routers.Response.Status).SendString(string(response)) + w.WriteHeader(h.Routers.Response.Status) + _, _ = w.Write(response) + } else { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write(matching.Result) } - - return c.Status(http.StatusBadRequest).SendString(string(matching.Result)) } // NewHandler a instance diff --git a/pkg/api/wiremock/router.go b/pkg/api/wiremock/router.go index 39e79e6..6fd4f57 100644 --- a/pkg/api/wiremock/router.go +++ b/pkg/api/wiremock/router.go @@ -1,22 +1,21 @@ package wiremock import ( - "github.com/gofiber/fiber/v2" + "github.com/gorilla/mux" "github.com/prongbang/wiremock/v2/pkg/config" "github.com/prongbang/wiremock/v2/pkg/status" - "gopkg.in/yaml.v2" "io/ioutil" ) type Router interface { - Initial(app *fiber.App) + Initial(route *mux.Router) } type route struct { UseCase UseCase } -func (r *route) Initial(app *fiber.App) { +func (r *route) Initial(route *mux.Router) { pattern := status.Pattern() @@ -30,15 +29,8 @@ func (r *route) Initial(app *fiber.App) { for _, f := range files { if f.IsDir() { - // Read yaml config - source := r.UseCase.ReadSourceRouteYml(f.Name()) - - // Unmarshal yaml config - routes := Routes{} - err = yaml.Unmarshal(source, &routes) - if err != nil { - panic(err) - } + // Get routes from yaml config + routes := r.UseCase.GetRoutes(f.Name()) // Register routers for rte := range routes.Routers { @@ -46,7 +38,7 @@ func (r *route) Initial(app *fiber.App) { request := routers.Request routers.Response.FileName = f.Name() handle := NewHandler(r.UseCase, routers) - app.Add(request.Method, request.URL, handle.Handle) + route.HandleFunc(request.URL, handle.Handle).Methods(request.Method) } } } diff --git a/pkg/api/wiremock/usecase.go b/pkg/api/wiremock/usecase.go index 0726d8f..23f2e08 100644 --- a/pkg/api/wiremock/usecase.go +++ b/pkg/api/wiremock/usecase.go @@ -3,34 +3,48 @@ package wiremock import ( "encoding/json" "fmt" - "github.com/gofiber/fiber/v2" "github.com/prongbang/wiremock/v2/pkg/config" "github.com/prongbang/wiremock/v2/pkg/core" "github.com/prongbang/wiremock/v2/pkg/status" + "gopkg.in/yaml.v2" "io/ioutil" + "net/http" ) type UseCase interface { - CasesMatching(c *fiber.Ctx, path string, cases map[string]Cases, params Parameters) CaseMatching + CasesMatching(r *http.Request, path string, cases map[string]Cases, params Parameters) CaseMatching ParameterMatching(params Parameters) Matching GetMockResponse(resp Response) []byte ReadSourceRouteYml(routeName string) []byte + GetRoutes(filepath string) Routes } type useCase struct { } -func (u *useCase) CasesMatching(c *fiber.Ctx, path string, cases map[string]Cases, params Parameters) CaseMatching { +func (u *useCase) GetRoutes(filepath string) Routes { + // Read yaml config + source := u.ReadSourceRouteYml(filepath) + + // Unmarshal yaml config + routes := Routes{} + err := yaml.Unmarshal(source, &routes) + if err != nil { + panic(err) + } + return routes +} + +func (u *useCase) CasesMatching(r *http.Request, path string, cases map[string]Cases, params Parameters) CaseMatching { // Get request - body := map[string]interface{}{} - _ = c.BodyParser(&body) + body := core.Body(r) // Process header matching require := map[string]interface{}{} errors := map[string]interface{}{} matchingHeader := 0 - for k, v := range params.ReqBody.MockBody { + for k, v := range params.ReqHeader.MockHeader { vs := fmt.Sprintf("%v", v) ks := fmt.Sprintf("%v", params.ReqHeader.HttpHeader[k]) if vs == ks { @@ -52,7 +66,7 @@ func (u *useCase) CasesMatching(c *fiber.Ctx, path string, cases map[string]Case if err != nil { result = []byte("{}") } - matchingHeaderRequest := len(params.ReqBody.MockBody) == matchingHeader + matchingHeaderRequest := len(params.ReqHeader.MockHeader) == matchingHeader // Process body matching matchingBodyRequest := false @@ -62,7 +76,7 @@ func (u *useCase) CasesMatching(c *fiber.Ctx, path string, cases map[string]Case matchingBody := 0 vMock.Response.FileName = path if len(body) == 0 { - body = core.BindCaseBody(vMock.Body, c) + body = core.BindCaseBody(vMock.Body, r) } for ck, cv := range vMock.Body { vs := fmt.Sprintf("%v", cv) diff --git a/pkg/core/bind.go b/pkg/core/bind.go deleted file mode 100644 index 553855a..0000000 --- a/pkg/core/bind.go +++ /dev/null @@ -1,59 +0,0 @@ -package core - -import ( - "bytes" - "encoding/json" - "github.com/gofiber/fiber/v2" -) - -func Body(c *fiber.Ctx) map[string]interface{} { - body := map[string]interface{}{} - b := c.Body() - _ = json.Unmarshal(b, &body) - return body -} - -func BodyDecode(c *fiber.Ctx, v interface{}) { - reader := bytes.NewReader(c.Body()) - _ = json.NewDecoder(reader).Decode(v) -} - -func BindHeader(mockHeader map[string]interface{}, c *fiber.Ctx) map[string]interface{} { - data := map[string]interface{}{} - for k := range mockHeader { - header := c.GetReqHeaders() - v := header[k] - if v != "" { - data[k] = v - } - } - return data -} - -func BindBody(mockBody map[string]interface{}, c *fiber.Ctx) map[string]interface{} { - data := map[string]interface{}{} - body := Body(c) - for k := range mockBody { - v := body[k] - if v != "" { - data[k] = v - } - } - if len(data) == 0 { - BodyDecode(c, &data) - } - return data -} - -func BindCaseBody(mockBody map[string]interface{}, c *fiber.Ctx) map[string]interface{} { - data := map[string]interface{}{} - body := fiber.Map{} - _ = c.BodyParser(&body) - for k := range mockBody { - v := body[k] - if v != "" { - data[k] = v - } - } - return data -} diff --git a/pkg/core/request.go b/pkg/core/request.go new file mode 100644 index 0000000..6e85fa0 --- /dev/null +++ b/pkg/core/request.go @@ -0,0 +1,61 @@ +package core + +import ( + "encoding/json" + "net/http" +) + +func Body(r *http.Request) map[string]interface{} { + data := map[string]interface{}{} + for k := range r.Form { + data[k] = r.FormValue(k) + } + if len(data) == 0 { + _ = json.NewDecoder(r.Body).Decode(&data) + } + return data +} + +func Header(r *http.Request) map[string]interface{} { + data := map[string]interface{}{} + for k := range r.Header { + data[k] = r.Header.Get(k) + } + return data +} + +func BindHeader(mockHeader map[string]interface{}, r *http.Request) map[string]interface{} { + data := map[string]interface{}{} + for k := range mockHeader { + v := r.Header.Get(k) + if v != "" { + data[k] = v + } + } + return data +} + +func BindBody(mockBody map[string]interface{}, r *http.Request) map[string]interface{} { + data := map[string]interface{}{} + for k := range mockBody { + v := r.FormValue(k) + if v != "" { + data[k] = v + } + } + if len(data) == 0 { + _ = json.NewDecoder(r.Body).Decode(&data) + } + return data +} + +func BindCaseBody(mockBody map[string]interface{}, r *http.Request) map[string]interface{} { + data := map[string]interface{}{} + for k := range mockBody { + v := r.FormValue(k) + if v != "" { + data[k] = v + } + } + return data +}