Restructure config and setup of handlers
- move initialization and dependency resolving to registration of protocol handlers - replace file opening in HTTP mock with fs.FS - depends on having a FS that returns an io.ReadSeeker - could be worked around in the future - update config for new routing rules to see how they look like in production - extend grammar tests
This commit is contained in:
parent
45a6d11d5b
commit
9f53b01e49
19 changed files with 248 additions and 215 deletions
|
@ -10,27 +10,18 @@ import (
|
|||
|
||||
"gitlab.com/inetmock/inetmock/internal/app"
|
||||
"gitlab.com/inetmock/inetmock/internal/endpoint"
|
||||
dns "gitlab.com/inetmock/inetmock/internal/endpoint/handler/dns/mock"
|
||||
http "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/mock"
|
||||
"gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/proxy"
|
||||
"gitlab.com/inetmock/inetmock/internal/endpoint/handler/metrics"
|
||||
"gitlab.com/inetmock/inetmock/pkg/cert"
|
||||
)
|
||||
|
||||
var (
|
||||
serverApp app.App
|
||||
cfg appConfig
|
||||
registrations = []endpoint.Registration{
|
||||
http.AddHTTPMock,
|
||||
dns.AddDNSMock,
|
||||
proxy.AddHTTPProxy,
|
||||
metrics.AddMetricsExporter,
|
||||
}
|
||||
serverApp app.App
|
||||
cfg appConfig
|
||||
)
|
||||
|
||||
type Data struct {
|
||||
PCAP string
|
||||
Audit string
|
||||
PCAP string
|
||||
Audit string
|
||||
FakeFiles string
|
||||
}
|
||||
|
||||
func (d *Data) setup() (err error) {
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"gitlab.com/inetmock/inetmock/internal/endpoint"
|
||||
dnsmock "gitlab.com/inetmock/inetmock/internal/endpoint/handler/dns/mock"
|
||||
"gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/mock"
|
||||
"gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/proxy"
|
||||
"gitlab.com/inetmock/inetmock/internal/endpoint/handler/metrics"
|
||||
"gitlab.com/inetmock/inetmock/internal/pcap"
|
||||
audit2 "gitlab.com/inetmock/inetmock/internal/pcap/consumers/audit"
|
||||
"gitlab.com/inetmock/inetmock/internal/rpc"
|
||||
|
@ -39,12 +46,7 @@ func startINetMock(_ *cobra.Command, _ []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
for _, registration := range registrations {
|
||||
if err = registration(registry); err != nil {
|
||||
appLogger.Error("Failed to run registration", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
}
|
||||
fakeFileFS := os.DirFS(cfg.Data.FakeFiles)
|
||||
|
||||
var certStore cert.Store
|
||||
if certStore, err = cert.NewDefaultStore(cfg.TLS, appLogger.Named("CertStore")); err != nil {
|
||||
|
@ -57,8 +59,12 @@ func startINetMock(_ *cobra.Command, _ []string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if err = setupEndpointHandlers(registry, appLogger, eventStream, certStore, fakeFileFS); err != nil {
|
||||
appLogger.Error("Failed to run registration", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
var endpointOrchestrator = endpoint.NewOrchestrator(
|
||||
serverApp.Context(),
|
||||
certStore,
|
||||
registry,
|
||||
eventStream,
|
||||
|
@ -86,7 +92,7 @@ func startINetMock(_ *cobra.Command, _ []string) error {
|
|||
}
|
||||
}
|
||||
|
||||
errChan := endpointOrchestrator.StartEndpoints()
|
||||
errChan := endpointOrchestrator.StartEndpoints(serverApp.Context())
|
||||
if err := rpcAPI.StartServer(); err != nil {
|
||||
serverApp.Shutdown()
|
||||
appLogger.Error(
|
||||
|
@ -148,6 +154,22 @@ func setupEventStream(appLogger logging.Logger) (audit.EventStream, error) {
|
|||
return evenStream, nil
|
||||
}
|
||||
|
||||
func setupEndpointHandlers(registry endpoint.HandlerRegistry, logger logging.Logger, emitter audit.Emitter, store cert.Store, fakeFileFS fs.FS) (err error) {
|
||||
if err = mock.AddHTTPMock(registry, logger, emitter, fakeFileFS); err != nil {
|
||||
return
|
||||
}
|
||||
if err = proxy.AddHTTPProxy(registry, logger, emitter, store); err != nil {
|
||||
return
|
||||
}
|
||||
if err = dnsmock.AddDNSMock(registry, logger, emitter); err != nil {
|
||||
return
|
||||
}
|
||||
if err = metrics.AddMetricsExporter(registry, logger); err != nil {
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//nolint:deadcode
|
||||
func startAuditConsumer(eventStream audit.EventStream) error {
|
||||
recorder := pcap.NewRecorder()
|
||||
|
|
55
config.yaml
55
config.yaml
|
@ -1,45 +1,19 @@
|
|||
x-response-rules: &httpResponseRules
|
||||
rules:
|
||||
- pattern: ".*\\.(?i)exe"
|
||||
matcher: Path
|
||||
- pattern: "^application/octet-stream$"
|
||||
target: Accept
|
||||
matcher: Header
|
||||
response: ./assets/fakeFiles/sample.exe
|
||||
- pattern: "^image/jpeg$"
|
||||
target: Accept
|
||||
matcher: Header
|
||||
response: ./assets/fakeFiles/default.jpg
|
||||
- pattern: ".*\\.(?i)(jpg|jpeg)"
|
||||
matcher: Path
|
||||
response: ./assets/fakeFiles/default.jpg
|
||||
- pattern: "^image/png$"
|
||||
target: Accept
|
||||
matcher: Header
|
||||
response: ./assets/fakeFiles/default.png
|
||||
- pattern: ".*\\.(?i)png"
|
||||
matcher: Path
|
||||
response: ./assets/fakeFiles/default.png
|
||||
- pattern: ".*\\.(?i)gif"
|
||||
matcher: Path
|
||||
response: ./assets/fakeFiles/default.gif
|
||||
- pattern: ".*\\.(?i)ico"
|
||||
matcher: Path
|
||||
response: ./assets/fakeFiles/default.ico
|
||||
- pattern: "^text/plain$"
|
||||
target: Accept
|
||||
matcher: Header
|
||||
response: ./assets/fakeFiles/default.txt
|
||||
- pattern: ".*\\.(?i)txt"
|
||||
matcher: Path
|
||||
response: ./assets/fakeFiles/default.txt
|
||||
- pattern: "^text/html$"
|
||||
target: Accept
|
||||
matcher: Header
|
||||
response: ./assets/fakeFiles/default.html
|
||||
- pattern: ".*"
|
||||
matcher: Path
|
||||
response: ./assets/fakeFiles/default.html
|
||||
- PathPattern("") => File("sample.exe")
|
||||
- Header("Accept", "application/octet-stream") => File("sample.exe")
|
||||
- Header("Accept", "image/jpeg") => File("default.jpg")
|
||||
- PathPattern(".*\\.(?i)(jpg|jpeg)") => File("default.jpg")
|
||||
- Header("Accept", "image/png") => File("default.png")
|
||||
- PathPattern(".*\\.(?i)png") => File("default.png")
|
||||
- Header("Accept", "image/gif") => File("default.gif")
|
||||
- PathPattern(".*\\.(?i)gif") => File("default.gif")
|
||||
- Header("Accept", "image/x-icon") => File("default.ico")
|
||||
- PathPattern(".*\\.(?i)ico") => File("default.ico")
|
||||
- Header("Accept", "text/plain") => File("default.txt")
|
||||
- PathPattern(".*\\.(?i)txt") => File("default.txt")
|
||||
- Header("Accept", "text/html") => File("default.html")
|
||||
- PathPattern(".*") => File("default.html")
|
||||
|
||||
x-http-handlers: &httpHandlers
|
||||
endpoints:
|
||||
|
@ -57,6 +31,7 @@ x-http-handlers: &httpHandlers
|
|||
data:
|
||||
pcap: /var/lib/inetmock/data/pcap
|
||||
audit: /var/lib/inetmock/data/audit
|
||||
fakeFiles: ./assets/fakeFiles
|
||||
|
||||
api:
|
||||
listen: unix:///var/run/inetmock.sock
|
||||
|
|
|
@ -6,24 +6,16 @@ import (
|
|||
"context"
|
||||
|
||||
"github.com/soheilhy/cmux"
|
||||
|
||||
"gitlab.com/inetmock/inetmock/pkg/audit"
|
||||
"gitlab.com/inetmock/inetmock/pkg/cert"
|
||||
"gitlab.com/inetmock/inetmock/pkg/logging"
|
||||
)
|
||||
|
||||
type Lifecycle interface {
|
||||
Name() string
|
||||
Logger() logging.Logger
|
||||
CertStore() cert.Store
|
||||
Audit() audit.Emitter
|
||||
Context() context.Context
|
||||
Uplink() Uplink
|
||||
UnmarshalOptions(cfg interface{}) error
|
||||
}
|
||||
|
||||
type ProtocolHandler interface {
|
||||
Start(ctx Lifecycle) error
|
||||
Start(ctx context.Context, lifecycle Lifecycle) error
|
||||
}
|
||||
|
||||
type MultiplexHandler interface {
|
||||
|
|
|
@ -5,6 +5,8 @@ import (
|
|||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"gitlab.com/inetmock/inetmock/pkg/logging"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -17,19 +19,19 @@ type Endpoint struct {
|
|||
uplink Uplink
|
||||
}
|
||||
|
||||
func (e *Endpoint) Start(lifecycle Lifecycle) (err error) {
|
||||
func (e *Endpoint) Start(ctx context.Context, logger logging.Logger, lifecycle Lifecycle) (err error) {
|
||||
startupResult := make(chan error)
|
||||
ctx, cancel := context.WithTimeout(lifecycle.Context(), startupTimeoutDuration)
|
||||
ctx, cancel := context.WithTimeout(ctx, startupTimeoutDuration)
|
||||
defer cancel()
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
lifecycle.Logger().Fatal("Startup error recovered", zap.Any("recovered", r))
|
||||
logger.Fatal("Startup error recovered", zap.Any("recovered", r))
|
||||
}
|
||||
}()
|
||||
|
||||
startupResult <- e.Handler.Start(lifecycle)
|
||||
startupResult <- e.Handler.Start(ctx, lifecycle)
|
||||
}()
|
||||
|
||||
select {
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"go.uber.org/zap"
|
||||
|
||||
"gitlab.com/inetmock/inetmock/internal/endpoint"
|
||||
"gitlab.com/inetmock/inetmock/pkg/audit"
|
||||
"gitlab.com/inetmock/inetmock/pkg/logging"
|
||||
)
|
||||
|
||||
|
@ -15,17 +16,18 @@ const shutdownTimeout = 100 * time.Millisecond
|
|||
|
||||
type dnsHandler struct {
|
||||
logger logging.Logger
|
||||
emitter audit.Emitter
|
||||
dnsServer *dns.Server
|
||||
}
|
||||
|
||||
func (d *dnsHandler) Start(lifecycle endpoint.Lifecycle) error {
|
||||
func (d *dnsHandler) Start(ctx context.Context, lifecycle endpoint.Lifecycle) error {
|
||||
var err error
|
||||
var options dnsOptions
|
||||
if options, err = loadFromConfig(lifecycle); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.logger = lifecycle.Logger().With(
|
||||
d.logger = d.logger.With(
|
||||
zap.String("handler_name", lifecycle.Name()),
|
||||
zap.String("address", lifecycle.Uplink().Addr().String()),
|
||||
)
|
||||
|
@ -33,8 +35,8 @@ func (d *dnsHandler) Start(lifecycle endpoint.Lifecycle) error {
|
|||
handler := ®exHandler{
|
||||
handlerName: lifecycle.Name(),
|
||||
fallback: options.Fallback,
|
||||
logger: lifecycle.Logger(),
|
||||
auditEmitter: lifecycle.Audit(),
|
||||
logger: d.logger,
|
||||
auditEmitter: d.emitter,
|
||||
}
|
||||
|
||||
for _, rule := range options.Rules {
|
||||
|
@ -59,6 +61,7 @@ func (d *dnsHandler) Start(lifecycle endpoint.Lifecycle) error {
|
|||
}
|
||||
|
||||
go d.startServer()
|
||||
go d.shutdownOnEnd(ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"gitlab.com/inetmock/inetmock/internal/endpoint"
|
||||
"gitlab.com/inetmock/inetmock/pkg/audit"
|
||||
"gitlab.com/inetmock/inetmock/pkg/logging"
|
||||
"gitlab.com/inetmock/inetmock/pkg/metrics"
|
||||
)
|
||||
|
||||
|
@ -21,7 +23,7 @@ var (
|
|||
initLock sync.Locker = new(sync.Mutex)
|
||||
)
|
||||
|
||||
func AddDNSMock(registry endpoint.HandlerRegistry) error {
|
||||
func AddDNSMock(registry endpoint.HandlerRegistry, logger logging.Logger, emitter audit.Emitter) error {
|
||||
initLock.Lock()
|
||||
defer initLock.Unlock()
|
||||
|
||||
|
@ -61,7 +63,10 @@ func AddDNSMock(registry endpoint.HandlerRegistry) error {
|
|||
}
|
||||
|
||||
registry.RegisterHandler(name, func() endpoint.ProtocolHandler {
|
||||
return &dnsHandler{}
|
||||
return &dnsHandler{
|
||||
logger: logger,
|
||||
emitter: emitter,
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
|
|
|
@ -3,6 +3,7 @@ package mock
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io/fs"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
|
@ -11,6 +12,7 @@ import (
|
|||
|
||||
"gitlab.com/inetmock/inetmock/internal/endpoint"
|
||||
imHttp "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http"
|
||||
"gitlab.com/inetmock/inetmock/pkg/audit"
|
||||
"gitlab.com/inetmock/inetmock/pkg/logging"
|
||||
)
|
||||
|
||||
|
@ -21,16 +23,18 @@ const (
|
|||
)
|
||||
|
||||
type httpHandler struct {
|
||||
logger logging.Logger
|
||||
server *http.Server
|
||||
logger logging.Logger
|
||||
fakeFileFS fs.FS
|
||||
server *http.Server
|
||||
emitter audit.Emitter
|
||||
}
|
||||
|
||||
func (p *httpHandler) Matchers() []cmux.Matcher {
|
||||
return []cmux.Matcher{cmux.HTTP1()}
|
||||
}
|
||||
|
||||
func (p *httpHandler) Start(lifecycle endpoint.Lifecycle) error {
|
||||
p.logger = lifecycle.Logger().With(
|
||||
func (p *httpHandler) Start(ctx context.Context, lifecycle endpoint.Lifecycle) error {
|
||||
p.logger = p.logger.With(
|
||||
zap.String("protocol_handler", name),
|
||||
)
|
||||
|
||||
|
@ -45,9 +49,10 @@ func (p *httpHandler) Start(lifecycle endpoint.Lifecycle) error {
|
|||
)
|
||||
|
||||
router := &RegexHandler{
|
||||
logger: p.logger,
|
||||
emitter: lifecycle.Audit(),
|
||||
handlerName: lifecycle.Name(),
|
||||
logger: p.logger,
|
||||
emitter: p.emitter,
|
||||
fakeFileFS: p.fakeFileFS,
|
||||
}
|
||||
p.server = &http.Server{
|
||||
Handler: router,
|
||||
|
@ -59,7 +64,7 @@ func (p *httpHandler) Start(lifecycle endpoint.Lifecycle) error {
|
|||
}
|
||||
|
||||
go p.startServer(lifecycle.Uplink().Listener)
|
||||
go p.shutdownOnCancel(lifecycle.Context())
|
||||
go p.shutdownOnCancel(ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package mock
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
|
@ -27,13 +28,15 @@ type RegexHandler struct {
|
|||
logger logging.Logger
|
||||
routes []*route
|
||||
emitter audit.Emitter
|
||||
fakeFileFS fs.FS
|
||||
}
|
||||
|
||||
func NewRegexHandler(name string, logger logging.Logger, emitter audit.Emitter) *RegexHandler {
|
||||
func NewRegexHandler(name string, logger logging.Logger, emitter audit.Emitter, fakeFileFS fs.FS) *RegexHandler {
|
||||
return &RegexHandler{
|
||||
handlerName: name,
|
||||
logger: logger,
|
||||
emitter: emitter,
|
||||
fakeFileFS: fakeFileFS,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,23 +71,32 @@ func (h *RegexHandler) AddRouteRule(rule TargetRule) {
|
|||
h.Handler(rule, emittingFileHandler{
|
||||
emitter: h.emitter,
|
||||
targetPath: rule.response,
|
||||
fs: h.fakeFileFS,
|
||||
})
|
||||
}
|
||||
|
||||
type emittingFileHandler struct {
|
||||
emitter audit.Emitter
|
||||
targetPath string
|
||||
fs fs.FS
|
||||
}
|
||||
|
||||
func (f emittingFileHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||
f.emitter.Emit(imHttp.EventFromRequest(request, v1.AppProtocol_APP_PROTOCOL_HTTP))
|
||||
file, err := os.Open(f.targetPath)
|
||||
file, err := f.fs.Open(f.targetPath)
|
||||
if err != nil {
|
||||
http.Error(writer, err.Error(), 500)
|
||||
}
|
||||
|
||||
var rs io.ReadSeeker
|
||||
var ok bool
|
||||
if rs, ok = file.(io.ReadSeeker); !ok {
|
||||
http.Error(writer, "internal server error", 500)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
//nolint:gosec
|
||||
http.ServeContent(writer, request, path.Base(request.RequestURI), time.Now().Add(-(time.Duration(rand.Int()) * time.Millisecond)), file)
|
||||
http.ServeContent(writer, request, path.Base(request.RequestURI), time.Now().Add(-(time.Duration(rand.Int()) * time.Millisecond)), rs)
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
package mock_test
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/maxatome/go-testdeep/td"
|
||||
|
@ -19,6 +20,19 @@ import (
|
|||
"gitlab.com/inetmock/inetmock/pkg/logging"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultHTMLContent = `<html>
|
||||
<head>
|
||||
<title>INetSim default HTML page</title>
|
||||
</head>
|
||||
<body>
|
||||
<p></p>
|
||||
<p align="center">This is the default HTML page for INetMock HTTP mock handler.</p>
|
||||
<p align="center">This file is an HTML document.</p>
|
||||
</body>
|
||||
</html>`
|
||||
)
|
||||
|
||||
//nolint:funlen
|
||||
func TestRegexpHandler_ServeHTTP(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
@ -28,25 +42,32 @@ func TestRegexpHandler_ServeHTTP(t *testing.T) {
|
|||
type fields struct {
|
||||
rules []mock.TargetRule
|
||||
emitterSetup func(tb testing.TB, ctrl *gomock.Controller) audit.Emitter
|
||||
fakeFileFS fs.FS
|
||||
}
|
||||
type args struct {
|
||||
req *http.Request
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantErr bool
|
||||
wantStatus interface{}
|
||||
wantRespHash string
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantErr bool
|
||||
wantStatus interface{}
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "GET /index.html",
|
||||
fields: fields{
|
||||
rules: []mock.TargetRule{
|
||||
mock.MustPathTargetRule(`\.(?i)(htm|html)$`, filepath.Join("testdata", "default.html")),
|
||||
mock.MustPathTargetRule(`\.(?i)(htm|html)$`, "default.html"),
|
||||
},
|
||||
emitterSetup: defaultEmitter,
|
||||
fakeFileFS: fstest.MapFS{
|
||||
"default.html": &fstest.MapFile{
|
||||
Data: []byte(defaultHTMLContent),
|
||||
ModTime: time.Now().Add(-1337 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
req: &http.Request{
|
||||
|
@ -54,16 +75,22 @@ func TestRegexpHandler_ServeHTTP(t *testing.T) {
|
|||
Method: http.MethodGet,
|
||||
},
|
||||
},
|
||||
wantRespHash: "c2a3f8995831dd1e79cb753619a55752692168f6cf846b07405f2070492f481c",
|
||||
wantStatus: td.Between(200, 299),
|
||||
want: defaultHTMLContent,
|
||||
wantStatus: td.Between(200, 299),
|
||||
},
|
||||
{
|
||||
name: "GET /profile.htm",
|
||||
fields: fields{
|
||||
rules: []mock.TargetRule{
|
||||
mock.MustPathTargetRule(`\.(?i)(htm|html)$`, filepath.Join("testdata", "default.html")),
|
||||
mock.MustPathTargetRule(`\.(?i)(htm|html)$`, "default.html"),
|
||||
},
|
||||
emitterSetup: defaultEmitter,
|
||||
fakeFileFS: fstest.MapFS{
|
||||
"default.html": &fstest.MapFile{
|
||||
Data: []byte(defaultHTMLContent),
|
||||
ModTime: time.Now().Add(-1337 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
req: &http.Request{
|
||||
|
@ -71,17 +98,23 @@ func TestRegexpHandler_ServeHTTP(t *testing.T) {
|
|||
Method: http.MethodGet,
|
||||
},
|
||||
},
|
||||
wantRespHash: "c2a3f8995831dd1e79cb753619a55752692168f6cf846b07405f2070492f481c",
|
||||
wantStatus: td.Between(200, 299),
|
||||
want: defaultHTMLContent,
|
||||
wantStatus: td.Between(200, 299),
|
||||
},
|
||||
{
|
||||
name: "GET with Accept: text/html",
|
||||
fields: fields{
|
||||
rules: []mock.TargetRule{
|
||||
mock.MustPathTargetRule(`\.(?i)(htm|html)$`, filepath.Join("testdata", "default.html")),
|
||||
mock.MustHeaderTargetRule("Accept", "(?i)text/html", filepath.Join("testdata", "default.html")),
|
||||
mock.MustPathTargetRule(`\.(?i)(htm|html)$`, "default.html"),
|
||||
mock.MustHeaderTargetRule("Accept", "(?i)text/html", "default.html"),
|
||||
},
|
||||
emitterSetup: defaultEmitter,
|
||||
fakeFileFS: fstest.MapFS{
|
||||
"default.html": &fstest.MapFile{
|
||||
Data: []byte(defaultHTMLContent),
|
||||
ModTime: time.Now().Add(-1337 * time.Second),
|
||||
},
|
||||
},
|
||||
},
|
||||
args: args{
|
||||
req: &http.Request{
|
||||
|
@ -92,8 +125,8 @@ func TestRegexpHandler_ServeHTTP(t *testing.T) {
|
|||
Method: http.MethodGet,
|
||||
},
|
||||
},
|
||||
wantRespHash: "c2a3f8995831dd1e79cb753619a55752692168f6cf846b07405f2070492f481c",
|
||||
wantStatus: td.Between(200, 299),
|
||||
want: defaultHTMLContent,
|
||||
wantStatus: td.Between(200, 299),
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
|
@ -102,7 +135,7 @@ func TestRegexpHandler_ServeHTTP(t *testing.T) {
|
|||
t.Parallel()
|
||||
logger := logging.CreateTestLogger(t)
|
||||
ctrl := gomock.NewController(t)
|
||||
h := mock.NewRegexHandler(t.Name(), logger, tt.fields.emitterSetup(t, ctrl))
|
||||
h := mock.NewRegexHandler(t.Name(), logger, tt.fields.emitterSetup(t, ctrl), tt.fields.fakeFileFS)
|
||||
|
||||
for _, rule := range tt.fields.rules {
|
||||
h.AddRouteRule(rule)
|
||||
|
@ -121,11 +154,10 @@ func TestRegexpHandler_ServeHTTP(t *testing.T) {
|
|||
|
||||
td.Cmp(t, resp.StatusCode, tt.wantStatus)
|
||||
|
||||
sha256Hash := sha256.New()
|
||||
_, err = io.Copy(sha256Hash, resp.Body)
|
||||
builder := new(strings.Builder)
|
||||
_, err = io.Copy(builder, resp.Body)
|
||||
td.CmpNoError(t, err)
|
||||
computedHash := hex.EncodeToString(sha256Hash.Sum(nil))
|
||||
td.Cmp(t, computedHash, tt.wantRespHash)
|
||||
td.Cmp(t, builder.String(), tt.want)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
package mock
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"sync"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"gitlab.com/inetmock/inetmock/internal/endpoint"
|
||||
"gitlab.com/inetmock/inetmock/pkg/audit"
|
||||
"gitlab.com/inetmock/inetmock/pkg/logging"
|
||||
"gitlab.com/inetmock/inetmock/pkg/metrics"
|
||||
)
|
||||
|
||||
|
@ -46,13 +49,16 @@ func InitMetrics() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func AddHTTPMock(registry endpoint.HandlerRegistry) (err error) {
|
||||
func AddHTTPMock(registry endpoint.HandlerRegistry, logger logging.Logger, emitter audit.Emitter, fakeFileFS fs.FS) (err error) {
|
||||
if err := InitMetrics(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
registry.RegisterHandler(name, func() endpoint.ProtocolHandler {
|
||||
return &httpHandler{}
|
||||
return &httpHandler{
|
||||
emitter: emitter,
|
||||
logger: logger,
|
||||
}
|
||||
})
|
||||
|
||||
return
|
||||
|
|
|
@ -12,6 +12,8 @@ import (
|
|||
|
||||
"gitlab.com/inetmock/inetmock/internal/endpoint"
|
||||
imHttp "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http"
|
||||
"gitlab.com/inetmock/inetmock/pkg/audit"
|
||||
"gitlab.com/inetmock/inetmock/pkg/cert"
|
||||
"gitlab.com/inetmock/inetmock/pkg/logging"
|
||||
)
|
||||
|
||||
|
@ -20,16 +22,18 @@ const (
|
|||
)
|
||||
|
||||
type httpProxy struct {
|
||||
logger logging.Logger
|
||||
proxy *goproxy.ProxyHttpServer
|
||||
server *http.Server
|
||||
logger logging.Logger
|
||||
proxy *goproxy.ProxyHttpServer
|
||||
certStore cert.Store
|
||||
emitter audit.Emitter
|
||||
server *http.Server
|
||||
}
|
||||
|
||||
func (h *httpProxy) Matchers() []cmux.Matcher {
|
||||
return []cmux.Matcher{cmux.HTTP1()}
|
||||
}
|
||||
|
||||
func (h *httpProxy) Start(lifecycle endpoint.Lifecycle) error {
|
||||
func (h *httpProxy) Start(ctx context.Context, lifecycle endpoint.Lifecycle) error {
|
||||
var opts httpProxyOptions
|
||||
if err := lifecycle.UnmarshalOptions(&opts); err != nil {
|
||||
return err
|
||||
|
@ -44,25 +48,25 @@ func (h *httpProxy) Start(lifecycle endpoint.Lifecycle) error {
|
|||
zap.String("address", lifecycle.Uplink().Addr().String()),
|
||||
)
|
||||
|
||||
tlsConfig := lifecycle.CertStore().TLSConfig()
|
||||
tlsConfig := h.certStore.TLSConfig()
|
||||
|
||||
proxyHandler := &proxyHTTPHandler{
|
||||
handlerName: lifecycle.Name(),
|
||||
options: opts,
|
||||
logger: h.logger,
|
||||
emitter: lifecycle.Audit(),
|
||||
emitter: h.emitter,
|
||||
}
|
||||
|
||||
proxyHTTPSHandler := &proxyHTTPSHandler{
|
||||
options: opts,
|
||||
tlsConfig: tlsConfig,
|
||||
emitter: lifecycle.Audit(),
|
||||
emitter: h.emitter,
|
||||
}
|
||||
|
||||
h.proxy.OnRequest().Do(proxyHandler)
|
||||
h.proxy.OnRequest().HandleConnect(proxyHTTPSHandler)
|
||||
go h.startProxy(lifecycle.Uplink().Listener)
|
||||
go h.shutdownOnContextDone(lifecycle.Context())
|
||||
go h.shutdownOnContextDone(ctx)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"go.uber.org/zap"
|
||||
|
||||
"gitlab.com/inetmock/inetmock/internal/endpoint"
|
||||
"gitlab.com/inetmock/inetmock/pkg/audit"
|
||||
"gitlab.com/inetmock/inetmock/pkg/cert"
|
||||
"gitlab.com/inetmock/inetmock/pkg/logging"
|
||||
"gitlab.com/inetmock/inetmock/pkg/metrics"
|
||||
)
|
||||
|
@ -15,11 +17,7 @@ var (
|
|||
requestDurationHistogram *prometheus.HistogramVec
|
||||
)
|
||||
|
||||
func AddHTTPProxy(registry endpoint.HandlerRegistry) (err error) {
|
||||
var logger logging.Logger
|
||||
if logger, err = logging.CreateLogger(); err != nil {
|
||||
return
|
||||
}
|
||||
func AddHTTPProxy(registry endpoint.HandlerRegistry, logger logging.Logger, emitter audit.Emitter, store cert.Store) (err error) {
|
||||
logger = logger.With(
|
||||
zap.String("protocol_handler", name),
|
||||
)
|
||||
|
@ -30,8 +28,10 @@ func AddHTTPProxy(registry endpoint.HandlerRegistry) (err error) {
|
|||
|
||||
registry.RegisterHandler(name, func() endpoint.ProtocolHandler {
|
||||
return &httpProxy{
|
||||
logger: logger,
|
||||
proxy: goproxy.NewProxyHttpServer(),
|
||||
logger: logger,
|
||||
emitter: emitter,
|
||||
certStore: store,
|
||||
proxy: goproxy.NewProxyHttpServer(),
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package metrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
|
@ -20,7 +21,7 @@ type metricsExporter struct {
|
|||
server *http.Server
|
||||
}
|
||||
|
||||
func (m *metricsExporter) Start(lifecycle endpoint.Lifecycle) error {
|
||||
func (m *metricsExporter) Start(ctx context.Context, lifecycle endpoint.Lifecycle) error {
|
||||
var exporterOptions metricsExporterOptions
|
||||
if err := lifecycle.UnmarshalOptions(&exporterOptions); err != nil {
|
||||
return err
|
||||
|
@ -47,7 +48,7 @@ func (m *metricsExporter) Start(lifecycle endpoint.Lifecycle) error {
|
|||
}()
|
||||
|
||||
go func() {
|
||||
<-lifecycle.Context().Done()
|
||||
<-ctx.Done()
|
||||
if err := m.server.Close(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
m.logger.Error("failed to stop metrics server", zap.Error(err))
|
||||
}
|
||||
|
|
|
@ -7,11 +7,7 @@ import (
|
|||
"gitlab.com/inetmock/inetmock/pkg/logging"
|
||||
)
|
||||
|
||||
func AddMetricsExporter(registry endpoint.HandlerRegistry) (err error) {
|
||||
var logger logging.Logger
|
||||
if logger, err = logging.CreateLogger(); err != nil {
|
||||
return
|
||||
}
|
||||
func AddMetricsExporter(registry endpoint.HandlerRegistry, logger logging.Logger) (err error) {
|
||||
logger = logger.With(
|
||||
zap.String("protocol_handler", name),
|
||||
)
|
||||
|
|
|
@ -1,40 +1,22 @@
|
|||
package endpoint
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
"gitlab.com/inetmock/inetmock/pkg/audit"
|
||||
"gitlab.com/inetmock/inetmock/pkg/cert"
|
||||
"gitlab.com/inetmock/inetmock/pkg/logging"
|
||||
)
|
||||
|
||||
type endpointLifecycle struct {
|
||||
endpointName string
|
||||
ctx context.Context
|
||||
logger logging.Logger
|
||||
certStore cert.Store
|
||||
emitter audit.Emitter
|
||||
uplink Uplink
|
||||
opts map[string]interface{}
|
||||
}
|
||||
|
||||
func NewEndpointLifecycleFromContext(
|
||||
func NewEndpointLifecycle(
|
||||
endpointName string,
|
||||
ctx context.Context,
|
||||
logger logging.Logger,
|
||||
certStore cert.Store,
|
||||
emitter audit.Emitter,
|
||||
uplink Uplink,
|
||||
opts map[string]interface{},
|
||||
) Lifecycle {
|
||||
return &endpointLifecycle{
|
||||
endpointName: endpointName,
|
||||
ctx: ctx,
|
||||
logger: logger,
|
||||
certStore: certStore,
|
||||
emitter: emitter,
|
||||
uplink: uplink,
|
||||
opts: opts,
|
||||
}
|
||||
|
@ -48,22 +30,6 @@ func (e *endpointLifecycle) Uplink() Uplink {
|
|||
return e.uplink
|
||||
}
|
||||
|
||||
func (e *endpointLifecycle) Logger() logging.Logger {
|
||||
return e.logger
|
||||
}
|
||||
|
||||
func (e *endpointLifecycle) CertStore() cert.Store {
|
||||
return e.certStore
|
||||
}
|
||||
|
||||
func (e *endpointLifecycle) Audit() audit.Emitter {
|
||||
return e.emitter
|
||||
}
|
||||
|
||||
func (e *endpointLifecycle) Context() context.Context {
|
||||
return e.ctx
|
||||
}
|
||||
|
||||
func (e *endpointLifecycle) UnmarshalOptions(cfg interface{}) error {
|
||||
return mapstructure.Decode(e.opts, cfg)
|
||||
}
|
||||
|
|
|
@ -18,18 +18,16 @@ var (
|
|||
|
||||
type Orchestrator interface {
|
||||
RegisterListener(spec ListenerSpec) error
|
||||
StartEndpoints() (errChan chan error)
|
||||
StartEndpoints(ctx context.Context) (errChan chan error)
|
||||
}
|
||||
|
||||
func NewOrchestrator(
|
||||
appCtx context.Context,
|
||||
certStore cert.Store,
|
||||
registry HandlerRegistry,
|
||||
emitter audit.Emitter,
|
||||
logger logging.Logger,
|
||||
) Orchestrator {
|
||||
return &orchestrator{
|
||||
appCtx: appCtx,
|
||||
registry: registry,
|
||||
logger: logger,
|
||||
certStore: certStore,
|
||||
|
@ -38,7 +36,6 @@ func NewOrchestrator(
|
|||
}
|
||||
|
||||
type orchestrator struct {
|
||||
appCtx context.Context
|
||||
registry HandlerRegistry
|
||||
logger logging.Logger
|
||||
certStore cert.Store
|
||||
|
@ -68,24 +65,20 @@ func (e *orchestrator) RegisterListener(spec ListenerSpec) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func (e *orchestrator) StartEndpoints() chan error {
|
||||
func (e *orchestrator) StartEndpoints(ctx context.Context) chan error {
|
||||
var errChan = make(chan error)
|
||||
for _, epListener := range e.endpointListeners {
|
||||
endpointLogger := e.logger.With(
|
||||
zap.String("epListener", epListener.name),
|
||||
)
|
||||
endpointLogger.Info("Starting epListener")
|
||||
lifecycle := NewEndpointLifecycleFromContext(
|
||||
lifecycle := NewEndpointLifecycle(
|
||||
epListener.name,
|
||||
e.appCtx,
|
||||
e.logger.With(zap.String("epListener", epListener.name)),
|
||||
e.certStore,
|
||||
e.emitter,
|
||||
epListener.uplink,
|
||||
epListener.Options,
|
||||
)
|
||||
|
||||
if err := epListener.Start(lifecycle); err == nil {
|
||||
if err := epListener.Start(ctx, e.logger.With(zap.String("epListener", epListener.name)), lifecycle); err == nil {
|
||||
endpointLogger.Info("successfully started epListener")
|
||||
} else {
|
||||
endpointLogger.Error("error occurred during epListener startup - will be skipped for now")
|
||||
|
|
|
@ -2,43 +2,61 @@ package endpoint_test
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"testing/fstest"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/maxatome/go-testdeep/td"
|
||||
|
||||
"gitlab.com/inetmock/inetmock/internal/endpoint"
|
||||
dnsmock "gitlab.com/inetmock/inetmock/internal/endpoint/handler/dns/mock"
|
||||
httpmock "gitlab.com/inetmock/inetmock/internal/endpoint/handler/http/mock"
|
||||
audit_mock "gitlab.com/inetmock/inetmock/internal/mock/audit"
|
||||
"gitlab.com/inetmock/inetmock/pkg/logging"
|
||||
)
|
||||
|
||||
func Test_handlerRegistry_AvailableHandlers(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
handlerRegistry endpoint.HandlerRegistry
|
||||
handlerRegistrySetup func(tb testing.TB, ctrl *gomock.Controller) endpoint.HandlerRegistry
|
||||
wantAvailableHandlers interface{}
|
||||
}{
|
||||
{
|
||||
name: "Empty registry",
|
||||
handlerRegistry: endpoint.NewHandlerRegistry(),
|
||||
name: "Empty registry",
|
||||
handlerRegistrySetup: func(testing.TB, *gomock.Controller) endpoint.HandlerRegistry {
|
||||
return endpoint.NewHandlerRegistry()
|
||||
},
|
||||
wantAvailableHandlers: td.Nil(),
|
||||
},
|
||||
{
|
||||
name: "Single handler registered",
|
||||
handlerRegistry: func() endpoint.HandlerRegistry {
|
||||
handlerRegistrySetup: func(tb testing.TB, ctrl *gomock.Controller) endpoint.HandlerRegistry {
|
||||
tb.Helper()
|
||||
registry := endpoint.NewHandlerRegistry()
|
||||
_ = httpmock.AddHTTPMock(registry)
|
||||
logger := logging.CreateTestLogger(tb)
|
||||
emitter := audit_mock.NewMockEmitter(ctrl)
|
||||
if err := httpmock.AddHTTPMock(registry, logger, emitter, new(fstest.MapFS)); err != nil {
|
||||
tb.Fatalf("AddHTTPMock() error = %v", err)
|
||||
}
|
||||
return registry
|
||||
}(),
|
||||
},
|
||||
wantAvailableHandlers: td.Set(endpoint.HandlerReference("http_mock")),
|
||||
},
|
||||
{
|
||||
name: "Multiple handler registered",
|
||||
handlerRegistry: func() endpoint.HandlerRegistry {
|
||||
handlerRegistrySetup: func(tb testing.TB, ctrl *gomock.Controller) endpoint.HandlerRegistry {
|
||||
tb.Helper()
|
||||
registry := endpoint.NewHandlerRegistry()
|
||||
_ = httpmock.AddHTTPMock(registry)
|
||||
_ = dnsmock.AddDNSMock(registry)
|
||||
logger := logging.CreateTestLogger(tb)
|
||||
emitter := audit_mock.NewMockEmitter(ctrl)
|
||||
if err := httpmock.AddHTTPMock(registry, logger, emitter, new(fstest.MapFS)); err != nil {
|
||||
tb.Fatalf("AddHTTPMock() error = %v", err)
|
||||
}
|
||||
if err := dnsmock.AddDNSMock(registry, logger, emitter); err != nil {
|
||||
tb.Fatalf("AddHTTPMock() error = %v", err)
|
||||
}
|
||||
return registry
|
||||
}(),
|
||||
},
|
||||
wantAvailableHandlers: td.Set(
|
||||
endpoint.HandlerReference("dns_mock"),
|
||||
endpoint.HandlerReference("http_mock"),
|
||||
|
@ -49,7 +67,8 @@ func Test_handlerRegistry_AvailableHandlers(t *testing.T) {
|
|||
tt := tc
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
gotAvailableHandlers := tt.handlerRegistry.AvailableHandlers()
|
||||
ctrl := gomock.NewController(t)
|
||||
gotAvailableHandlers := tt.handlerRegistrySetup(t, ctrl).AvailableHandlers()
|
||||
td.Cmp(t, gotAvailableHandlers, tt.wantAvailableHandlers)
|
||||
})
|
||||
}
|
||||
|
@ -58,26 +77,34 @@ func Test_handlerRegistry_AvailableHandlers(t *testing.T) {
|
|||
func Test_handlerRegistry_HandlerForName(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
handlerRegistry endpoint.HandlerRegistry
|
||||
handlerRef endpoint.HandlerReference
|
||||
wantInstance interface{}
|
||||
wantOk bool
|
||||
name string
|
||||
handlerRegistrySetup func(tb testing.TB, ctrl *gomock.Controller) endpoint.HandlerRegistry
|
||||
handlerRef endpoint.HandlerReference
|
||||
wantInstance interface{}
|
||||
wantOk bool
|
||||
}{
|
||||
{
|
||||
name: "Empty registry",
|
||||
handlerRegistry: endpoint.NewHandlerRegistry(),
|
||||
handlerRef: "http_mock",
|
||||
wantInstance: nil,
|
||||
wantOk: false,
|
||||
name: "Empty registry",
|
||||
handlerRegistrySetup: func(tb testing.TB, _ *gomock.Controller) endpoint.HandlerRegistry {
|
||||
tb.Helper()
|
||||
return endpoint.NewHandlerRegistry()
|
||||
},
|
||||
handlerRef: "http_mock",
|
||||
wantInstance: nil,
|
||||
wantOk: false,
|
||||
},
|
||||
{
|
||||
name: "Registry with HTTP mock registered",
|
||||
handlerRegistry: func() endpoint.HandlerRegistry {
|
||||
handlerRegistrySetup: func(tb testing.TB, ctrl *gomock.Controller) endpoint.HandlerRegistry {
|
||||
tb.Helper()
|
||||
registry := endpoint.NewHandlerRegistry()
|
||||
_ = httpmock.AddHTTPMock(registry)
|
||||
logger := logging.CreateTestLogger(tb)
|
||||
emitter := audit_mock.NewMockEmitter(ctrl)
|
||||
if err := httpmock.AddHTTPMock(registry, logger, emitter, new(fstest.MapFS)); err != nil {
|
||||
tb.Fatalf("AddHTTPMock() error = %v", err)
|
||||
}
|
||||
return registry
|
||||
}(),
|
||||
},
|
||||
handlerRef: "http_mock",
|
||||
wantInstance: td.NotNil(),
|
||||
wantOk: true,
|
||||
|
@ -87,7 +114,8 @@ func Test_handlerRegistry_HandlerForName(t *testing.T) {
|
|||
tt := tc
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
gotInstance, gotOk := tt.handlerRegistry.HandlerForName(tt.handlerRef)
|
||||
ctrl := gomock.NewController(t)
|
||||
gotInstance, gotOk := tt.handlerRegistrySetup(t, ctrl).HandlerForName(tt.handlerRef)
|
||||
td.Cmp(t, gotInstance, tt.wantInstance)
|
||||
td.Cmp(t, gotOk, tt.wantOk)
|
||||
})
|
||||
|
|
|
@ -22,11 +22,11 @@ func TestParse(t *testing.T) {
|
|||
{
|
||||
name: "Terminator only - string argument",
|
||||
args: args{
|
||||
rule: `=> ReturnFile("default.html")`,
|
||||
rule: `=> File("default.html")`,
|
||||
},
|
||||
want: &Routing{
|
||||
Terminator: &Method{
|
||||
Name: "ReturnFile",
|
||||
Name: "File",
|
||||
Params: []Param{
|
||||
{
|
||||
String: stringRef("default.html"),
|
||||
|
@ -73,7 +73,7 @@ func TestParse(t *testing.T) {
|
|||
{
|
||||
name: "path pattern and terminator",
|
||||
args: args{
|
||||
rule: `PathPattern("/index.html") => ReturnFile("default.html")`,
|
||||
rule: `PathPattern(".*\\.(?i)png") => ReturnFile("default.html")`,
|
||||
},
|
||||
want: &Routing{
|
||||
Terminator: &Method{
|
||||
|
@ -90,7 +90,7 @@ func TestParse(t *testing.T) {
|
|||
Name: "PathPattern",
|
||||
Params: []Param{
|
||||
{
|
||||
String: stringRef("/index.html"),
|
||||
String: stringRef(`.*\.(?i)png`),
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
Loading…
Add table
Reference in a new issue