Skip to content

Commit

Permalink
support for yaml based configurations for mock endpoints
Browse files Browse the repository at this point in the history
Signed-off-by: wasim-nihal <[email protected]>
  • Loading branch information
wasim-nihal committed Mar 3, 2024
1 parent 7b176ac commit 4f37589
Show file tree
Hide file tree
Showing 6 changed files with 209 additions and 70 deletions.
24 changes: 24 additions & 0 deletions examples/example-1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
endpoints:
/hello:
- method: GET
content: text/plain
body: Hello, World! (GET)
status: 200
- method: POST
content: text/plain
body: Hello, World! (POST)
status: 200
/bye:
- method: GET
content: text/plain
body: Goodbye!
status: 400

## WIP (currently not available)
# fileServer:
# enable:
# serverDirectory:
# serverFiles:
# -
# -
# -
9 changes: 6 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
module mock-http-server

go 1.20
go 1.19

require github.com/prometheus/client_golang v1.18.0
require (
github.com/prometheus/client_golang v1.18.0
gopkg.in/yaml.v2 v2.4.0
)

require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.46.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
Expand Down
23 changes: 9 additions & 14 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,26 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
github.com/prometheus/common v0.46.0 h1:doXzt5ybi1HBKpsZOL0sSkaNHJJqkyfEWZGGqqScV0Y=
github.com/prometheus/common v0.46.0/go.mod h1:Tp0qkxpb9Jsg54QMe+EAmqXkSV7Evdy1BTn+g2pa/hQ=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
27 changes: 27 additions & 0 deletions http/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package httpserver

import (
"flag"
"net/http"
)

var (
basicAuthUsername = flag.String("basicauth.username", "", "username for basic authentication")
basicAuthPassword = flag.String("basicauth.password", "", "password for basic authentication")
)

func CheckBasicAuth(w http.ResponseWriter, r *http.Request) bool {
if len(*basicAuthUsername) == 0 {
// HTTP Basic Auth is disabled.
return true
}
username, password, ok := r.BasicAuth()
if ok {
if username == *basicAuthUsername && password == *basicAuthPassword {
return true
}
}
w.Header().Set("WWW-Authenticate", `Basic realm="MockServer"`)
http.Error(w, "", http.StatusUnauthorized)
return false
}
129 changes: 129 additions & 0 deletions http/server.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package httpserver

import (
"crypto/tls"
"flag"
"fmt"
"log"
"net"
"net/http"
"os"

"github.com/prometheus/client_golang/prometheus/promhttp"
"gopkg.in/yaml.v2"
)

var (
ConfigFile = flag.String("server.config", "", "path to server configuration file")
tlsEnable = flag.Bool("tls", false, "Whether to enable TLS for incoming HTTP requests at configured endpoints. -tlsCertFile and -tlsKeyFile must be set if -tls is set. ")
tlsCertFile = flag.String("tlsCertFile", "", "Path to file with TLS certificate if -tls is set.")
tlsKeyFile = flag.String("tlsKeyFile", "", "Path to file with TLS key if -tls is set.")
)

// Config represents the YAML configuration structure
type Config struct {
Endpoints map[string][]EndpointConfig `yaml:"endpoints"`
TlsConfig TlsConfig `yaml:"tls"`
FileServer FileServer `yaml:"fileServer"`
}

type FileServer struct {
Enable bool `yaml:"enable"`
ServeDir string `yaml:"serveDirectory"`
ServeFiles []string `yaml:"serveFiles"`
}
type TlsConfig struct {
ServerCert string `yaml:"serverCert"`
ServerKey string `yaml:"serverKey"`
CaCert string `yaml:"caCert"`
EnableMtls bool `yaml:"enableMtls"`
}

// EndpointConfig represents the configuration for each endpoint
type EndpointConfig struct {
Method string `yaml:"method"`
Content string `yaml:"content"`
Body string `yaml:"body"`
Status int `yaml:"status"`
}

// MockServer is a mock HTTP server based on the YAML configuration
type MockServer struct {
config Config
}

// NewMockServer creates a new instance of MockServer with the provided YAML configuration
func NewMockServer() (*MockServer, error) {
var cfg Config

// Read YAML configuration file
data, err := os.ReadFile(*ConfigFile)
if err != nil {
return nil, err
}
// Unmarshal YAML data into Config struct
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil, err
}
return &MockServer{
config: cfg,
}, nil
}

// MetricsHandler handles requests for Prometheus metrics
func MetricsHandler() http.Handler {
return promhttp.Handler()
}

// Handler handles incoming HTTP requests and returns mock responses based on the configuration
func (s *MockServer) Handler(w http.ResponseWriter, r *http.Request) {
if !CheckBasicAuth(w, r) {
return
}
endpoint := r.URL.Path
method := r.Method

if endpointConfigs, ok := s.config.Endpoints[endpoint]; ok {
var config *EndpointConfig
for _, cfg := range endpointConfigs {
if method == cfg.Method {
config = &cfg
}
}
if config != nil {
w.Header().Set("Content-Type", config.Content)
w.WriteHeader(config.Status)
fmt.Fprintf(w, config.Body)
return
}
}
fmt.Fprintf(w, "endpoint not configured\n")
// Return 404 if the endpoint or method is not configured
http.NotFound(w, r)
}

func GetListener(addr string) (*net.Listener, error) {
var tlsConfig *tls.Config
var cert tls.Certificate
var err error
if *tlsEnable {
cert, err = tls.LoadX509KeyPair(*tlsCertFile, *tlsKeyFile)
if err != nil {
log.Printf("unable to load the tls certificates. reason: %s", err.Error())
return nil, err
}
tlsConfig = &tls.Config{
GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) {
return &cert, nil
},
}
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return nil, err
}
if tlsConfig != nil {
ln = tls.NewListener(ln, tlsConfig)
}
return &ln, err
}
67 changes: 14 additions & 53 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,67 +4,28 @@ import (
"flag"
"fmt"
"log"
httpserver "mock-http-server/http"
"net/http"

"github.com/prometheus/client_golang/prometheus/promhttp"
)

var (
username = flag.String("username", "", "Username for basic authentication")
password = flag.String("password", "", "Password for basic authentication")
port = flag.Int("port", 8080, "http port for server")
port = flag.String("port", "8080", "http port for server")
)

func main() {
flag.Parse()
var handler, metricsHandler func(w http.ResponseWriter, r *http.Request)
if *username != "" && *password != "" {
log.Println("Started Http Server with basic auth enabled")
handler = handleWithBasicAuth(serverHandler, *username, *password)
metricsHandler = handleWithBasicAuth(promhttp.Handler().ServeHTTP, *username, *password)
} else {
log.Println("Started Http Server with basic auth disabled")
handler = serverHandler
metricsHandler = promhttp.Handler().ServeHTTP
// Create a new mock server with the provided YAML configuration
mockServer, err := httpserver.NewMockServer()
if err != nil {
log.Fatalf("Failed to create mock server: %v", err)
}
http.HandleFunc("/metrics", metricsHandler)
http.HandleFunc("/", handler)
port := *port
addr := fmt.Sprintf(":%d", port)
fmt.Printf("Server started at http://localhost:%d\n", port)
log.Fatal(http.ListenAndServe(addr, nil))
}

func serverHandler(w http.ResponseWriter, r *http.Request) {
// Log request details
log.Printf("Received %s request from %s with payload: %s\n", r.Method, r.RemoteAddr, extractPayload(r))

// Dummy response
response := "Hello, this is a dummy response!"
w.WriteHeader(http.StatusOK)
w.Write([]byte(response))
}

func handleWithBasicAuth(handler http.HandlerFunc, username, password string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
user, pass, ok := r.BasicAuth()

if !ok || user != username || pass != password {
log.Printf("Unauthorized access attempt from %s with incorrect credentials\n", r.RemoteAddr)
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}

handler(w, r)
}
}

func extractPayload(r *http.Request) string {
if r.Method == http.MethodPost {
body := make([]byte, r.ContentLength)
r.Body.Read(body)
return string(body)
http.HandleFunc("/", mockServer.Handler)
http.HandleFunc("/metrics", httpserver.MetricsHandler().ServeHTTP)
// Start the mock server on port 8080
log.Printf("Mock server is running on port %s...", *port)
ln, err := httpserver.GetListener(fmt.Sprintf("127.0.0.1:%s", *port))
if err != nil {
log.Fatal(err)
}
return ""
http.Serve(*ln, nil)
}

0 comments on commit 4f37589

Please sign in to comment.