Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,9 @@ ui/node_modules
# Jetbrains files
.idea/

# VSCode
.vscode/

# Local run output files
wiretap-report.json
__debug*
24 changes: 21 additions & 3 deletions cmd/handle_http_traffic.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,28 @@ package cmd

import (
"fmt"
"net/http"

"github.com/google/uuid"
"github.com/gorilla/handlers"
"github.com/pb33f/ranch/model"
"github.com/pb33f/wiretap/daemon"
"github.com/pb33f/wiretap/shared"
staticMock "github.com/pb33f/wiretap/static-mock"
"github.com/pterm/pterm"
"net/http"
)

func handleHttpTraffic(wiretapConfig *shared.WiretapConfiguration, wtService *daemon.WiretapService) {
type HandleHttpTraffic struct {
WiretapConfig *shared.WiretapConfiguration
WiretapService *daemon.WiretapService
StaticMockService *staticMock.StaticMockService
}

func handleHttpTraffic(hht *HandleHttpTraffic) {
wiretapConfig := hht.WiretapConfig
wtService := hht.WiretapService
staticMockService := hht.StaticMockService

go func() {
handleTraffic := func(w http.ResponseWriter, r *http.Request) {
id, _ := uuid.NewUUID()
Expand All @@ -24,7 +36,13 @@ func handleHttpTraffic(wiretapConfig *shared.WiretapConfiguration, wtService *da
HttpRequest: r,
HttpResponseWriter: w,
}
wtService.HandleHttpRequest(requestModel)

// if static-mock-dir is set, then we call the handler of staticMockService
if len(wiretapConfig.StaticMockDir) != 0 {
staticMockService.HandleStaticMockRequest(requestModel)
} else { // else call the wiretap service handler
wtService.HandleHttpRequest(requestModel)
}
Comment on lines +40 to +45
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should make static mocking path available for path globs. This way, one singular wiretap server can serve static mocks, openapi generated mocks, and also be a proxy ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is still a single server. This doesn't create a new server. We only add a new service that follows a different codepath when static mocking is enabled.

This way, one singular wiretap server can serve static mocks, openapi generated mocks, and also be a proxy ;)

This is already happening. :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Im having trouble understanding. According to the code, if staticMockDir is set, then no matter what, every single http request will go to the staticMockHandler. Right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but if there are no matches, it will be forwarded "back" to wiretapService.HandleHttpRequest. Look at line 195 in staticMock/handle_static_mock_request.go.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

handleWebsocket := func(w http.ResponseWriter, r *http.Request) {
Expand Down
22 changes: 22 additions & 0 deletions cmd/root_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ var (
var hardErrorCode int
var hardErrorReturnCode int

// static mock dir
var staticMockDir string

// mock mode
var mockMode bool
var useAllMockResponseFields bool
Expand All @@ -84,6 +87,7 @@ var (
harWhiteList, _ := cmd.Flags().GetStringArray("har-allow")

debug, _ := cmd.Flags().GetBool("debug")
staticMockDir, _ = cmd.Flags().GetString("static-mock-dir")
mockMode, _ = cmd.Flags().GetBool("mock-mode")
useAllMockResponseFields, _ = cmd.Flags().GetBool("enable-all-mock-response-fields")
hardError, _ = cmd.Flags().GetBool("hard-validation")
Expand Down Expand Up @@ -181,6 +185,11 @@ var (
if config.Spec != "" {
spec = config.Spec
}
if len(staticMockDir) != 0 {
if len(config.StaticMockDir) == 0 {
config.StaticMockDir = staticMockDir
}
}
if mockMode {
if !config.MockMode {
config.MockMode = true
Expand Down Expand Up @@ -223,6 +232,11 @@ var (

pterm.Info.Println("No wiretap configuration located. Using defaults")
config.StaticIndex = staticIndex
if len(staticMockDir) != 0 {
if len(config.StaticMockDir) == 0 {
config.StaticMockDir = staticMockDir
}
}
if mockMode {
config.MockMode = true
}
Expand Down Expand Up @@ -433,6 +447,13 @@ var (
pterm.Println()
}

// static mock dir
if len(config.StaticMockDir) != 0 {
pterm.Printf("Ⓜ️ %s. Requests matching mock definitions in the static-mock-dir will return mocked responses.\n",
pterm.LightCyan("Static mock directory defined"))
pterm.Println()
}

// mock mode
if config.MockMode {
pterm.Printf("Ⓜ️ %s. All responses will be mocked and no traffic will be sent to the target API.\n",
Expand Down Expand Up @@ -689,6 +710,7 @@ func Execute(version, commit, date string, fs embed.FS) {
rootCmd.Flags().BoolP("hard-validation", "e", false, "Return a HTTP error for non-compliant request/response")
rootCmd.Flags().IntP("hard-validation-code", "q", 400, "Set a custom http error code for non-compliant requests when using the hard-error flag")
rootCmd.Flags().IntP("hard-validation-return-code", "y", 502, "Set a custom http error code for non-compliant responses when using the hard-error flag")
rootCmd.Flags().StringP("static-mock-dir", "", "", "Directory containing static mock definitions. All requests matching these definitions will return mocked responses.")
rootCmd.Flags().BoolP("mock-mode", "x", false, "Run in mock mode, responses are mocked and no traffic is sent to the target API (requires OpenAPI spec)")
rootCmd.Flags().BoolP("enable-all-mock-response-fields", "o", true, "Enable usage of all property examples in mock responses. When set to false, only required field examples will be used.")
rootCmd.Flags().StringP("config", "c", "", "Location of wiretap configuration file to use (default is .wiretap in current directory)")
Expand Down
22 changes: 18 additions & 4 deletions cmd/run_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
package cmd

import (
"os"
"reflect"
"strconv"

"github.com/pb33f/libopenapi"
"github.com/pb33f/ranch/bus"
"github.com/pb33f/ranch/plank/pkg/server"
Expand All @@ -14,9 +18,7 @@ import (
"github.com/pb33f/wiretap/report"
"github.com/pb33f/wiretap/shared"
"github.com/pb33f/wiretap/specs"
"os"
"reflect"
"strconv"
staticMock "github.com/pb33f/wiretap/static-mock"
)

func runWiretapService(wiretapConfig *shared.WiretapConfiguration, doc libopenapi.Document) (server.PlatformServer, error) {
Expand Down Expand Up @@ -70,6 +72,13 @@ func runWiretapService(wiretapConfig *shared.WiretapConfiguration, doc libopenap
panic(err)
}

staticMockService := staticMock.NewStaticMockService(wtService, wiretapConfig.Logger)
// register Static-Mock Service
if err = platformServer.RegisterService(
staticMockService, staticMock.StaticMockServiceChan); err != nil {
panic(err)
}

Comment on lines +75 to +81
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the ranch makes it so easy to add new features 🙏

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, this is all code for async APIs, under the covers ranch is wiring up this service against a message bus (that is what the channel is for). You can then talk to this service using pub/sub over a websocket.

// register spec service
if err = platformServer.RegisterService(
specs.NewSpecService(doc), specs.SpecServiceChan); err != nil {
Expand Down Expand Up @@ -107,7 +116,12 @@ func runWiretapService(wiretapConfig *shared.WiretapConfiguration, doc libopenap
bootedMessage(wiretapConfig)

// boot the http handler
handleHttpTraffic(wiretapConfig, wtService)
hht := HandleHttpTraffic{
WiretapConfig: wiretapConfig,
WiretapService: wtService,
StaticMockService: staticMockService,
}
handleHttpTraffic(&hht)

// boot the monitor
serveMonitor(wiretapConfig)
Expand Down
9 changes: 5 additions & 4 deletions daemon/handle_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ package daemon
import (
"bytes"
"fmt"
"github.com/pb33f/ranch/model"
configModel "github.com/pb33f/wiretap/config"
"github.com/pb33f/wiretap/shared"
"io"
"net/http"
"time"

"github.com/pb33f/ranch/model"
configModel "github.com/pb33f/wiretap/config"
"github.com/pb33f/wiretap/shared"
)

func (ws *WiretapService) handleMockRequest(
Expand All @@ -38,7 +39,7 @@ func (ws *WiretapService) handleMockRequest(

// wiretap needs to work from anywhere, so allow everything.
headers := make(map[string][]string)
setCORSHeaders(headers)
shared.SetCORSHeaders(headers)
headers["Content-Type"] = []string{"application/json"}

buff := bytes.NewBuffer(mock)
Expand Down
8 changes: 1 addition & 7 deletions daemon/handle_request.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ func (ws *WiretapService) handleHttpRequest(request *model.Request) {
headers := ExtractHeaders(returnedResponse)

// wiretap needs to work from anywhere, so allow everything.
setCORSHeaders(headers)
shared.SetCORSHeaders(headers)

if config.StrictRedirectLocation && is3xxStatusCode(returnedResponse.StatusCode) {
setStrictLocationHeader(config, headers)
Expand Down Expand Up @@ -378,12 +378,6 @@ func (ws *WiretapService) handleWebsocketRequest(request *model.Request) {
}
}

func setCORSHeaders(headers map[string][]string) {
headers["Access-Control-Allow-Headers"] = []string{"*"}
headers["Access-Control-Allow-Origin"] = []string{"*"}
headers["Access-Control-Allow-Methods"] = []string{"OPTIONS,POST,GET,DELETE,PATCH,PUT"}
}

// setStrictLocationHeader rewrites any `Location` headers to wiretap's ApiGatewayHost. Some web servers specify
// the full URL when redirecting the browser, so we need to ensure that the browser isn't redirected away from the
// wiretap Host. We achieve this by rewriting the `Location` header host and port to wiretap's host and port on all
Expand Down
44 changes: 44 additions & 0 deletions daemon/handle_static_mock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2023-2024 Princess Beef Heavy Industries, LLC / Dave Shanley
// https://pb33f.io
// SPDX-License-Identifier: AGPL

package daemon

import (
"fmt"
"io"
"net/http"

"github.com/pb33f/ranch/model"
)

func (ws *WiretapService) handleStaticMockResponse(request *model.Request, response *http.Response) {
// validate response async
go ws.broadcastResponse(request, response)

for k, v := range response.Header {
for _, j := range v {
request.HttpResponseWriter.Header().Set(k, fmt.Sprint(j))
}
}

responseCodeToReturn := 200
if response.StatusCode != 0 {
responseCodeToReturn = response.StatusCode
}
request.HttpResponseWriter.WriteHeader(responseCodeToReturn)

if response.Body == nil {
return
}

byteArrayBody, err := io.ReadAll(response.Body)
if err != nil {
panic(err)
}

_, errs := request.HttpResponseWriter.Write(byteArrayBody)
if errs != nil {
panic(errs)
}
}
13 changes: 10 additions & 3 deletions daemon/wiretap_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@
package daemon

import (
"net/http"
"time"

"github.com/pb33f/libopenapi"
"github.com/pb33f/libopenapi-validator/errors"
"github.com/pb33f/libopenapi/datamodel/high/v3"
v3 "github.com/pb33f/libopenapi/datamodel/high/v3"
"github.com/pb33f/ranch/bus"
"github.com/pb33f/ranch/model"
"github.com/pb33f/ranch/service"
"github.com/pb33f/wiretap/controls"
"github.com/pb33f/wiretap/mock"
"github.com/pb33f/wiretap/shared"
"github.com/pb33f/wiretap/validation"
"net/http"
"time"
)

const (
Expand All @@ -42,6 +43,7 @@ type WiretapService struct {
streamChan chan []*errors.ValidationError
streamViolations []*errors.ValidationError
reportFile string
StaticMockDir string
}

func NewWiretapService(document libopenapi.Document, config *shared.WiretapConfiguration) *WiretapService {
Expand All @@ -61,6 +63,7 @@ func NewWiretapService(document libopenapi.Document, config *shared.WiretapConfi
transport: tr,
controlsStore: controlsStore,
transactionStore: transactionStore,
StaticMockDir: config.StaticMockDir,
}
if document != nil {
m, _ := document.BuildV3Model()
Expand Down Expand Up @@ -99,6 +102,10 @@ func (ws *WiretapService) HandleHttpRequest(request *model.Request) {
ws.handleHttpRequest(request)
}

func (ws *WiretapService) HandleStaticMockResponse(request *model.Request, response *http.Response) {
ws.handleStaticMockResponse(request, response)
}

func (ws *WiretapService) HandleWebsocketRequest(request *model.Request) {
ws.handleWebsocketRequest(request)
}
1 change: 1 addition & 0 deletions shared/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type WiretapConfiguration struct {
PathDelays map[string]int `json:"pathDelays,omitempty" yaml:"pathDelays,omitempty"`
MockMode bool `json:"mockMode,omitempty" yaml:"mockMode,omitempty"`
MockModeList []string `json:"mockModeList,omitempty" yaml:"mockModeList,omitempty"`
StaticMockDir string `json:"staticMockDir,omitempty" yaml:"staticMockDir,omitempty"`
UseAllMockResponseFields bool `json:"useAllMockResponseFields,omitempty" yaml:"useAllMockResponseFields,omitempty"`
MockModePretty bool `json:"mockModePretty,omitempty" yaml:"mockModePretty,omitempty"`
Base string `json:"base,omitempty" yaml:"base,omitempty"`
Expand Down
7 changes: 7 additions & 0 deletions shared/header.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package shared

func SetCORSHeaders(headers map[string][]string) {
headers["Access-Control-Allow-Headers"] = []string{"*"}
headers["Access-Control-Allow-Origin"] = []string{"*"}
headers["Access-Control-Allow-Methods"] = []string{"OPTIONS,POST,GET,DELETE,PATCH,PUT"}
}
Loading
Loading